diff --git a/src/components/button/button.spec.ts b/src/components/button/button.spec.ts index e01f9b5fe85..f9c22f4b65b 100644 --- a/src/components/button/button.spec.ts +++ b/src/components/button/button.spec.ts @@ -1,15 +1,18 @@ +import { + inject, + injectAsync, + ComponentFixture, + TestComponentBuilder, + beforeEachProviders, +} from 'angular2/testing'; import { it, iit, describe, ddescribe, expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders, beforeEach, -} from 'angular2/testing'; +} from '../../core/facade/testing'; import {provide, Component, DebugElement} from 'angular2/core'; import {By} from 'angular2/platform/browser'; diff --git a/src/components/button/button.ts b/src/components/button/button.ts index 3c44b8536e8..efd1f241f80 100644 --- a/src/components/button/button.ts +++ b/src/components/button/button.ts @@ -1,4 +1,12 @@ -import {Component, ViewEncapsulation, Input, HostBinding, HostListener, ChangeDetectionStrategy} from 'angular2/core'; +import { + Component, + ViewEncapsulation, + Input, + HostBinding, + HostListener, + ChangeDetectionStrategy, +} from 'angular2/core'; +import {TimerWrapper} from 'angular2/src/facade/async'; // TODO(jelbourn): Ink ripples. // TODO(jelbourn): Make the `isMouseDown` stuff done with one global listener. @@ -37,7 +45,7 @@ export class MdButton { // button continues to look :active after clicking. // @see http://marcysutton.com/button-focus-hell/ this.isMouseDown = true; - setTimeout(() => { this.isMouseDown = false; }, 100); + TimerWrapper.setTimeout(() => { this.isMouseDown = false; }, 100); } setKeyboardFocus($event: any) { diff --git a/src/components/sidenav/sidenav.spec.ts b/src/components/sidenav/sidenav.spec.ts index 6e00c9d08a9..31287fb95b8 100644 --- a/src/components/sidenav/sidenav.spec.ts +++ b/src/components/sidenav/sidenav.spec.ts @@ -1,30 +1,34 @@ +import { + inject, + injectAsync, + ComponentFixture, + TestComponentBuilder, + beforeEachProviders, +} from 'angular2/testing'; import { it, iit, describe, ddescribe, expect, - inject, - injectAsync, - TestComponentBuilder, - beforeEachProviders, beforeEach, -} from 'angular2/testing'; +} from '../../core/facade/testing'; +import { + getComputedStyle +} from '../../core/facade/html'; import {provide, Component, DebugElement} from 'angular2/core'; import {By} from 'angular2/platform/browser'; - -import {MdSidenav, MdSidenavLayout, MD_SIDENAV_DIRECTIVES} from './sidenav'; -import {AsyncTestFn, FunctionWithParamTokens} from 'angular2/testing'; -import {ComponentFixture} from 'angular2/testing'; -import {EventEmitter} from 'angular2/core'; import {Predicate} from 'angular2/src/facade/collection'; import {PromiseWrapper} from 'angular2/src/facade/promise'; import {TimerWrapper} from 'angular2/src/facade/async'; +import {MdSidenav, MdSidenavLayout, MD_SIDENAV_DIRECTIVES} from './sidenav'; function wait(msec: number) { let completer = PromiseWrapper.completer(); - TimerWrapper.setTimeout(completer.resolve, msec); + TimerWrapper.setTimeout(() => { + completer.resolve(null); + }, msec); return completer.promise; } @@ -38,7 +42,7 @@ function waitOnEvent(fixture: ComponentFixture, let completer = PromiseWrapper.completer(); let component: any = fixture.debugElement.query(by).componentInstance; component[propertyName].subscribe(() => { - completer.resolve(); + completer.resolve(null); }); return completer.promise; } @@ -63,7 +67,7 @@ export function main() { testComponent = fixture.debugElement.componentInstance; fixture.detectChanges(); return wait(1); - }).then((f) => { + }).then((_: any) => { let openButtonElement = fixture.debugElement.query(By.css('.open')); openButtonElement.nativeElement.click(); fixture.detectChanges(); @@ -81,8 +85,8 @@ export function main() { let sidenavElement = fixture.debugElement.query(By.css('md-sidenav')); let sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop')); - expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('visible'); - expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('visible'); + expect(getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('visible'); + expect(getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('visible'); // Close it. let closeButtonElement = fixture.debugElement.query(By.css('.close')); @@ -107,10 +111,10 @@ export function main() { let sidenavElement = fixture.debugElement.query(By.css('md-sidenav')); let sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop')); - expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('hidden'); - expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('hidden'); + expect(getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('hidden'); + expect(getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('hidden'); }) - .then(done, done.fail); + .then((_: any) => { done(); }); }, 8000); it('open() and close() return a promise that resolves after the animation ended', @@ -149,7 +153,7 @@ export function main() { }) .then((_: any) => promise) .then((_: any) => expect(called).toBe(true)) - .then(done, done.fail); + .then((_: any) => { done(); }); }, 8000); it('open() twice returns the same promise', (done: any) => { @@ -175,7 +179,7 @@ export function main() { promise = sidenav.close(); expect(sidenav.close()).toBe(promise); }) - .then(done, done.fail); + .then((_: any) => { done(); }); }); it('open() then close() cancel animations when called too fast', @@ -183,7 +187,6 @@ export function main() { let fixture: ComponentFixture; let sidenav: MdSidenav; - let openPromise: Promise; let closePromise: Promise; let openCalled: boolean = false; let openCancelled: boolean = false; @@ -194,7 +197,7 @@ export function main() { fixture = f; sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; - openPromise = sidenav.open().then((_: any) => { + sidenav.open().then((_: any) => { openCalled = true; }, () => { @@ -208,7 +211,7 @@ export function main() { .then((_: any) => { closePromise = sidenav.close().then((_: any) => { closeCalled = true; - }, done.fail); + }); return wait(1); }) .then((_: any) => { @@ -220,17 +223,15 @@ export function main() { expect(openCancelled).toBe(true); expect(closeCalled).toBe(true); }) - .then(done, done.fail); + .then((_: any) => { done(); }); }, 8000); it('close() then open() cancel animations when called too fast', (done: any) => { - let testComponent: BasicTestApp; let fixture: ComponentFixture; let sidenav: MdSidenav; let openPromise: Promise; - let closePromise: Promise; let closeCalled: boolean = false; let closeCancelled: boolean = false; let openCalled: boolean = false; @@ -250,10 +251,9 @@ export function main() { }) .then((_: any) => { // Then close and check behavior. - closePromise = sidenav.close().then((_: any) => { + sidenav.close().then((_: any) => { closeCalled = true; - }, - () => { + }, () => { closeCancelled = true; }); }) @@ -276,7 +276,7 @@ export function main() { expect(closeCancelled).toBe(true); expect(openCalled).toBe(true); }) - .then(done, done.fail); + .then((_: any) => { done(); }); }, 8000); }); }); diff --git a/src/components/sidenav/sidenav.ts b/src/components/sidenav/sidenav.ts index e3f95ac6758..91e4216fe5d 100644 --- a/src/components/sidenav/sidenav.ts +++ b/src/components/sidenav/sidenav.ts @@ -3,7 +3,6 @@ import { Component, ContentChildren, ElementRef, - EventEmitter, Host, HostBinding, HostListener, @@ -14,7 +13,8 @@ import { Type, ChangeDetectionStrategy } from 'angular2/core'; -import {PromiseWrapper} from 'angular2/src/facade/promise'; +import {PromiseWrapper, ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; +import {iterateListLike} from 'angular2/src/facade/collection'; import {BaseException} from 'angular2/src/facade/exceptions'; import {CONST_EXPR, isPresent} from 'angular2/src/facade/lang'; import {Dir} from '../../directives/dir/dir'; @@ -128,9 +128,6 @@ export class MdSidenav { this.onCloseStart.emit(null); } - let emitter = isOpen ? this.onOpen : this.onClose; - let other = isOpen ? this.onClose : this.onOpen; - if (isOpen) { if (this.openPromise_ == null) { let completer = PromiseWrapper.completer(); @@ -259,13 +256,13 @@ export class MdSidenavLayout implements AfterContentInit { // If a `Dir` directive exists up the tree, listen direction changes and update the left/right // properties to point to the proper start/end. if (dir_ != null) { - dir_.dirChange.subscribe(() => this.validateDrawers_()); + dir_.dirChange.add(() => this.validateDrawers_()); } } ngAfterContentInit() { // On changes, assert on consistency. - this.sidenavs_.changes.subscribe(() => this.validateDrawers_()); + ObservableWrapper.subscribe(this.sidenavs_.changes, () => this.validateDrawers_()); this.validateDrawers_(); } @@ -294,17 +291,17 @@ export class MdSidenavLayout implements AfterContentInit { } // Ensure that we have at most one start and one end sidenav. - this.sidenavs_.toArray().forEach(drawer => { - if (drawer.align == 'end') { + iterateListLike(this.sidenavs_, (sidenav: any) => { + if (sidenav.align == 'end') { if (this.end_ != null) { throw new MdDuplicatedSidenavException('end'); } - this.end_ = drawer; + this.end_ = sidenav; } else { if (this.start_ != null) { throw new MdDuplicatedSidenavException('start'); } - this.start_ = drawer; + this.start_ = sidenav; } }); diff --git a/src/core/annotations/one-of.dart b/src/core/annotations/one-of.dart index fa3c5ef13ed..2fb1568820e 100644 --- a/src/core/annotations/one-of.dart +++ b/src/core/annotations/one-of.dart @@ -3,5 +3,5 @@ * TODO(hansl): Implement this properly in Dart. */ class OneOf { - const OneOf(List values); + const OneOf(List values); } diff --git a/src/core/facade/html.dart b/src/core/facade/html.dart new file mode 100644 index 00000000000..6346104490a --- /dev/null +++ b/src/core/facade/html.dart @@ -0,0 +1,5 @@ +import "dart:html"; + +CssStyleDeclaration getComputedStyle(element, [String pseudoElement]) { + return element.getComputedStyle(pseudoElement); +} diff --git a/src/core/facade/html.ts b/src/core/facade/html.ts new file mode 100644 index 00000000000..8e7c8b7c4a9 --- /dev/null +++ b/src/core/facade/html.ts @@ -0,0 +1,4 @@ + +export function getComputedStyle(element: HTMLElement, pseudoElt?: string) { + return window.getComputedStyle(element, pseudoElt); +} diff --git a/src/core/facade/matchers.dart b/src/core/facade/matchers.dart new file mode 100644 index 00000000000..5384a3df5e2 --- /dev/null +++ b/src/core/facade/matchers.dart @@ -0,0 +1,132 @@ +library testing.matchers; + +import 'dart:async'; + +import 'package:guinness/guinness.dart' as gns; + +import 'package:angular2/src/platform/dom/dom_adapter.dart' show DOM; + +import 'package:angular2/src/facade/lang.dart' show isString; + +Expect expect(actual, [matcher]) { + final expect = new Expect(actual); + if (matcher != null) expect.to(matcher); + return expect; +} + +const _u = const Object(); + +bool elementContainsStyle(element, styles) { + var allPassed = true; + if (isString(styles)) { + allPassed = DOM.hasStyle(element, styles); + } else { + styles.forEach((prop, style) { + allPassed = allPassed && DOM.hasStyle(element, prop, style); + }); + } + return allPassed; +} + +expectErrorMessage(actual, expectedMessage) { + expect(actual.toString()).toContain(expectedMessage); +} + +expectException(Function actual, expectedMessage) { + try { + actual(); + } catch (e, s) { + expectErrorMessage(e, expectedMessage); + } +} + +class Expect extends gns.Expect { + Expect(actual) : super(actual); + + NotExpect get not => new NotExpect(actual); + + void toEqual(expected) => toHaveSameProps(expected); + void toContainError(message) => expectErrorMessage(this.actual, message); + void toThrowError([message = ""]) => toThrowWith(message: message); + void toThrowErrorWith(message) => expectException(this.actual, message); + void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future); + void toHaveCssClass(className) => + gns.guinness.matchers.toBeTrue(DOM.hasClass(actual, className)); + void toHaveCssStyle(styles) { + gns.guinness.matchers.toBeTrue(elementContainsStyle(actual, styles)); + } + void toImplement(expected) => toBeA(expected); + void toBeNaN() => + gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0); + void toHaveText(expected) => _expect(elementText(actual), expected); + void toHaveBeenCalledWith([a = _u, b = _u, c = _u, d = _u, e = _u, f = _u]) => + _expect(_argsMatch(actual, a, b, c, d, e, f), true, + reason: 'method invoked with correct arguments'); + Function get _expect => gns.guinness.matchers.expect; + + // TODO(tbosch): move this hack into Guinness + _argsMatch(spyFn, [a0 = _u, a1 = _u, a2 = _u, a3 = _u, a4 = _u, a5 = _u]) { + var calls = spyFn.calls; + final toMatch = _takeDefined([a0, a1, a2, a3, a4, a5]); + if (calls.isEmpty) { + return false; + } else { + gns.SamePropsMatcher matcher = new gns.SamePropsMatcher(toMatch); + for (var i = 0; i < calls.length; i++) { + var call = calls[i]; + // TODO: create a better error message, not just 'Expected: Actual: '. + // For hacking this is good: + // print(call.positionalArguments); + if (matcher.matches(call.positionalArguments, null)) { + return true; + } + } + return false; + } + } + + List _takeDefined(List iter) => iter.takeWhile((_) => _ != _u).toList(); +} + +class NotExpect extends gns.NotExpect { + NotExpect(actual) : super(actual); + + void toEqual(expected) => toHaveSameProps(expected); + void toBePromise() => gns.guinness.matchers.toBeFalse(actual is Future); + void toHaveCssClass(className) => + gns.guinness.matchers.toBeFalse(DOM.hasClass(actual, className)); + void toHaveCssStyle(styles) { + gns.guinness.matchers.toBeFalse(elementContainsStyle(actual, styles)); + } + void toBeNull() => gns.guinness.matchers.toBeFalse(actual == null); + Function get _expect => gns.guinness.matchers.expect; +} + +String elementText(n) { + hasNodes(n) { + var children = DOM.childNodes(n); + return children != null && children.length > 0; + } + + if (n is Iterable) { + return n.map(elementText).join(""); + } + + if (DOM.isCommentNode(n)) { + return ''; + } + + if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') { + return elementText(DOM.getDistributedNodes(n)); + } + + if (DOM.hasShadowRoot(n)) { + return elementText(DOM.childNodesAsList(DOM.getShadowRoot(n))); + } + + if (hasNodes(n)) { + return elementText(DOM.childNodesAsList(n)); + } + + return DOM.getText(n); +} diff --git a/src/core/facade/test_injector.ts b/src/core/facade/test_injector.ts new file mode 100644 index 00000000000..e89114f29af --- /dev/null +++ b/src/core/facade/test_injector.ts @@ -0,0 +1,156 @@ +import {Injector, Provider, PLATFORM_INITIALIZER} from 'angular2/core'; +import {BaseException, ExceptionHandler} from 'angular2/src/facade/exceptions'; +import {ListWrapper} from 'angular2/src/facade/collection'; +import {FunctionWrapper, isPresent, Type} from 'angular2/src/facade/lang'; + +export class TestInjector { + private _instantiated: boolean = false; + + private _injector: Injector = null; + + private _providers: Array = []; + + reset() { + this._injector = null; + this._providers = []; + this._instantiated = false; + } + + platformProviders: Array = []; + + applicationProviders: Array = []; + + addProviders(providers: Array) { + if (this._instantiated) { + throw new BaseException('Cannot add providers after test injector is instantiated'); + } + this._providers = ListWrapper.concat(this._providers, providers); + } + + createInjector() { + var rootInjector = Injector.resolveAndCreate(this.platformProviders); + this._injector = rootInjector.resolveAndCreateChild( + ListWrapper.concat(this.applicationProviders, this._providers)); + this._instantiated = true; + return this._injector; + } + + execute(fn: FunctionWithParamTokens): any { + if (!this._instantiated) { + this.createInjector(); + } + return fn.execute(this._injector); + } +} + +var _testInjector: TestInjector = null; + +export function getTestInjector() { + if (_testInjector == null) { + _testInjector = new TestInjector(); + } + return _testInjector; +} + +/** + * Set the providers that the test injector should use. These should be providers + * common to every test in the suite. + * + * This may only be called once, to set up the common providers for the current test + * suite on teh current platform. If you absolutely need to change the providers, + * first use `resetBaseTestProviders`. + * + * Test Providers for individual platforms are available from + * 'angular2/platform/testing/'. + */ +export function setBaseTestProviders(platformProviders: Array, + applicationProviders: Array) { + var testInjector = getTestInjector(); + if (testInjector.platformProviders.length > 0 || testInjector.applicationProviders.length > 0) { + throw new BaseException('Cannot set base providers because it has already been called'); + } + testInjector.platformProviders = platformProviders; + testInjector.applicationProviders = applicationProviders; + var injector = testInjector.createInjector(); + let inits: Function[] = injector.getOptional(PLATFORM_INITIALIZER); + if (isPresent(inits)) { + inits.forEach(init => init()); + } + testInjector.reset(); +} + +/** + * Reset the providers for the test injector. + */ +export function resetBaseTestProviders() { + var testInjector = getTestInjector(); + testInjector.platformProviders = []; + testInjector.applicationProviders = []; + testInjector.reset(); +} + +/** + * Allows injecting dependencies in `beforeEach()` and `it()`. + * + * Example: + * + * ``` + * beforeEach(inject([Dependency, AClass], (dep, object) => { + * // some code that uses `dep` and `object` + * // ... + * })); + * + * it('...', inject([AClass], (object) => { + * object.doSomething(); + * expect(...); + * }) + * ``` + * + * Notes: + * - inject is currently a function because of some Traceur limitation the syntax should + * eventually + * becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });` + * + * @param {Array} tokens + * @param {Function} fn + * @return {FunctionWithParamTokens} + */ +export function inject(tokens: any[], fn: Function): FunctionWithParamTokens { + return new FunctionWithParamTokens(tokens, fn, false); +} + +/** + * Allows injecting dependencies in `beforeEach()` and `it()`. The test must return + * a promise which will resolve when all asynchronous activity is complete. + * + * Example: + * + * ``` + * it('...', injectAsync([AClass], (object) => { + * return object.doSomething().then(() => { + * expect(...); + * }); + * }) + * ``` + * + * @param {Array} tokens + * @param {Function} fn + * @return {FunctionWithParamTokens} + */ +export function injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens { + return new FunctionWithParamTokens(tokens, fn, true); +} + +export class FunctionWithParamTokens { + constructor(private _tokens: any[], private _fn: Function, public isAsync: boolean) {} + + /** + * Returns the value of the executed function. + */ + execute(injector: Injector): any { + var params = this._tokens.map(t => injector.get(t)); + return FunctionWrapper.apply(this._fn, params); + } + + hasToken(token: any): boolean { return this._tokens.indexOf(token) > -1; } +} diff --git a/src/core/facade/testing.dart b/src/core/facade/testing.dart new file mode 100644 index 00000000000..a404aab3bdd --- /dev/null +++ b/src/core/facade/testing.dart @@ -0,0 +1,166 @@ +library angular2.src.testing.testing_internal; + +import 'dart:async'; + +import 'package:guinness/guinness.dart' as gns; +export 'package:guinness/guinness.dart' + hide + Expect, + expect, + NotExpect, + beforeEach, + it, + iit, + xit, + SpyObject, + SpyFunction; + +export 'matchers.dart' show expect, Expect, NotExpect; + +import 'package:angular2/src/core/reflection/reflection.dart'; +import 'package:angular2/src/core/reflection/reflection_capabilities.dart'; + +import 'package:angular2/src/core/di/provider.dart' show bind; +import 'package:angular2/src/facade/collection.dart' show StringMapWrapper; + +import 'test_injector.dart'; +export 'test_injector.dart' show inject; + +TestInjector _testInjector = getTestInjector(); +bool _isCurrentTestAsync; +Future _currentTestFuture; +bool _inIt = false; + +class AsyncTestCompleter { + final _completer = new Completer(); + + AsyncTestCompleter() { + _currentTestFuture = this.future; + } + + void done() { + _completer.complete(); + } + + Future get future => _completer.future; +} + +void testSetup() { + reflector.reflectionCapabilities = new ReflectionCapabilities(); + // beforeEach configuration: + // - Priority 3: clear the bindings before each test, + // - Priority 2: collect the bindings before each test, see beforeEachProviders(), + // - Priority 1: create the test injector to be used in beforeEach() and it() + + gns.beforeEach(() { + _testInjector.reset(); + _currentTestFuture = null; + }, priority: 3); + + var completerProvider = bind(AsyncTestCompleter).toFactory(() { + // Mark the test as async when an AsyncTestCompleter is injected in an it(), + if (!_inIt) throw 'AsyncTestCompleter can only be injected in an "it()"'; + _isCurrentTestAsync = true; + return new AsyncTestCompleter(); + }); + + gns.beforeEach(() { + _isCurrentTestAsync = false; + _testInjector.addProviders([completerProvider]); + }, priority: 1); +} + +/** + * Allows overriding default providers defined in test_injector.js. + * + * The given function must return a list of DI providers. + * + * Example: + * + * beforeEachProviders(() => [ + * bind(Compiler).toClass(MockCompiler), + * bind(SomeToken).toValue(myValue), + * ]); + */ +void beforeEachProviders(Function fn) { + gns.beforeEach(() { + var providers = fn(); + if (providers != null) _testInjector.addProviders(providers); + }, priority: 2); +} + +@Deprecated('using beforeEachProviders instead') +void beforeEachBindings(Function fn) { + beforeEachProviders(fn); +} + +void beforeEach(fn) { + if (fn is! FunctionWithParamTokens) fn = + new FunctionWithParamTokens([], fn, false); + gns.beforeEach(() { + _testInjector.execute(fn); + }); +} + +void _it(gnsFn, name, fn) { + if (fn is! FunctionWithParamTokens) fn = + new FunctionWithParamTokens([], fn, false); + gnsFn(name, () { + _inIt = true; + _testInjector.execute(fn); + _inIt = false; + if (_isCurrentTestAsync) return _currentTestFuture; + }); +} + +void it(name, fn, [timeOut = null]) { + _it(gns.it, name, fn); +} + +void iit(name, fn, [timeOut = null]) { + _it(gns.iit, name, fn); +} + +void xit(name, fn, [timeOut = null]) { + _it(gns.xit, name, fn); +} + +class SpyFunction extends gns.SpyFunction { + SpyFunction(String name) : super(name); + + // TODO: vsavkin move to guinness + andReturn(value) { + return andCallFake(([a0, a1, a2, a3, a4, a5]) => value); + } +} + +class SpyObject extends gns.SpyObject { + final Map _spyFuncs = {}; + + SpyObject([arg]) {} + + SpyFunction spy(String funcName) => + _spyFuncs.putIfAbsent(funcName, () => new SpyFunction(funcName)); + + void prop(String funcName, value) { + _spyFuncs + .putIfAbsent("get:${funcName}", () => new SpyFunction(funcName)) + .andReturn(value); + } + + static stub([object = null, config = null, overrides = null]) { + if (object is! SpyObject) { + overrides = config; + config = object; + object = new SpyObject(); + } + + var m = StringMapWrapper.merge(config, overrides); + StringMapWrapper.forEach(m, (value, key) { + object.spy(key).andReturn(value); + }); + return object; + } +} + +bool isInInnerZone() => Zone.current['_innerZone'] == true; diff --git a/src/core/facade/testing.ts b/src/core/facade/testing.ts new file mode 100644 index 00000000000..7c7d80b63a0 --- /dev/null +++ b/src/core/facade/testing.ts @@ -0,0 +1,8 @@ +export { + it, + iit, + describe, + ddescribe, + expect, + beforeEach, +} from 'angular2/testing'; diff --git a/src/directives/dir/dir.ts b/src/directives/dir/dir.ts index b5ea5b8f020..56c0da895f2 100644 --- a/src/directives/dir/dir.ts +++ b/src/directives/dir/dir.ts @@ -1,4 +1,5 @@ -import {Directive, HostBinding, EventEmitter, Output, Input} from 'angular2/core'; +import {EventEmitter} from 'angular2/src/facade/async'; +import {Directive, HostBinding, Output, Input} from 'angular2/core'; import {OneOf} from "../../core/annotations/one-of";