Skip to content

Commit 438c2b3

Browse files
committed
test(TestBed): initial implementation
1 parent 57e308d commit 438c2b3

File tree

9 files changed

+218
-13
lines changed

9 files changed

+218
-13
lines changed

modules/angular2/core.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './src/core/annotations/visibility';
22
export * from './src/core/compiler/interfaces';
33
export * from './src/core/annotations/template';
44
export * from './src/core/application';
5+
export * from './src/core/application_tokens';
56
export * from './src/core/annotations/di';
67

78
export * from './src/core/compiler/compiler';

modules/angular2/src/core/application.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
2828
import {Component} from 'angular2/src/core/annotations/annotations';
2929
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
3030
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
31+
import {
32+
appViewToken,
33+
appChangeDetectorToken,
34+
appElementToken,
35+
appComponentAnnotatedTypeToken,
36+
appDocumentToken,
37+
} from './application_tokens';
3138

3239
var _rootInjector: Injector;
3340

@@ -37,12 +44,6 @@ var _rootBindings = [
3744
TestabilityRegistry
3845
];
3946

40-
export var appViewToken = new OpaqueToken('AppView');
41-
export var appChangeDetectorToken = new OpaqueToken('AppChangeDetector');
42-
export var appElementToken = new OpaqueToken('AppElement');
43-
export var appComponentAnnotatedTypeToken = new OpaqueToken('AppComponentAnnotatedType');
44-
export var appDocumentToken = new OpaqueToken('AppDocument');
45-
4647
function _injectorBindings(appComponentType): List<Binding> {
4748
return [
4849
bind(appDocumentToken).toValue(DOM.defaultDoc()),
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {OpaqueToken} from 'angular2/di';
2+
3+
export var appViewToken = new OpaqueToken('AppView');
4+
export var appChangeDetectorToken = new OpaqueToken('AppChangeDetector');
5+
export var appElementToken = new OpaqueToken('AppElement');
6+
export var appComponentAnnotatedTypeToken = new OpaqueToken('AppComponentAnnotatedType');
7+
export var appDocumentToken = new OpaqueToken('AppDocument');
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
2+
3+
export class MockVmTurnZone extends VmTurnZone {
4+
constructor() {
5+
super({enableLongStackTrace: false});
6+
}
7+
8+
run(fn) {
9+
fn();
10+
}
11+
12+
runOutsideAngular(fn) {
13+
fn();
14+
}
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'dart:mirrors';
2+
3+
Type getTypeOf(instance) => instance.runtimeType;
4+
5+
dynamic instantiateType(Type type, [List params = const []]) {
6+
var cm = reflectClass(type);
7+
return cm.newInstance(new Symbol(''), params).reflectee;
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function getTypeOf(instance) {
2+
return instance.constructor;
3+
}
4+
5+
export function instantiateType(type: Function, params: Array = []) {
6+
var instance = Object.create(type.prototype);
7+
instance.constructor.apply(instance, params);
8+
return instance;
9+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {Injector} from 'angular2/di';
2+
3+
import {Type, isPresent, BaseException} from 'angular2/src/facade/lang';
4+
import {Promise} from 'angular2/src/facade/async';
5+
import {isBlank} from 'angular2/src/facade/lang';
6+
import {List} from 'angular2/src/facade/collection';
7+
8+
import {Template} from 'angular2/src/core/annotations/template';
9+
10+
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
11+
import {Compiler} from 'angular2/src/core/compiler/compiler';
12+
import {View} from 'angular2/src/core/compiler/view';
13+
14+
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
15+
16+
import {queryView} from './utils';
17+
import {instantiateType, getTypeOf} from './lang_utils';
18+
19+
export class TestBed {
20+
_injector: Injector;
21+
22+
constructor(injector: Injector) {
23+
this._injector = injector;
24+
}
25+
26+
/**
27+
* Overrides the [Template] of a [Component].
28+
*
29+
* @see setInlineTemplate() to only override the html
30+
*
31+
* @param {Type} component
32+
* @param {Template} template
33+
*/
34+
overrideTemplate(component: Type, template: Template): void {
35+
this._injector.get(TemplateResolver).setTemplate(component, template);
36+
}
37+
38+
/**
39+
* Overrides only the html of a [Component].
40+
* All the other propoerties of the component's [Template] are preserved.
41+
*
42+
* @param {Type} component
43+
* @param {string} html
44+
*/
45+
setInlineTemplate(component: Type, html: string): void {
46+
this._injector.get(TemplateResolver).setInlineTemplate(component, html);
47+
}
48+
49+
/**
50+
* Overrides the directives from the component [Template].
51+
*
52+
* @param {Type} component
53+
* @param {Type} from
54+
* @param {Type} to
55+
*/
56+
overrideDirective(component: Type, from: Type, to: Type): void {
57+
this._injector.get(TemplateResolver).overrideTemplateDirective(component, from, to);
58+
}
59+
60+
/**
61+
* Creates a [View] for the given component.
62+
*
63+
* Only either a component or a context needs to be specified but both can be provided for
64+
* advanced use cases (ie subclassing the context).
65+
*
66+
* @param {Type} component
67+
* @param {*} context
68+
* @param {string} html Use as the component template when specified (shortcut for setInlineTemplate)
69+
* @return {Promise<ViewProxy>}
70+
*/
71+
createView(component: Type,
72+
{context = null, html = null}: {context:any, html: string} = {}): Promise<View> {
73+
74+
if (isBlank(component) && isBlank(context)) {
75+
throw new BaseException('You must specified at least a component or a context');
76+
}
77+
78+
if (isBlank(component)) {
79+
component = getTypeOf(context);
80+
} else if (isBlank(context)) {
81+
context = instantiateType(component);
82+
}
83+
84+
if (isPresent(html)) {
85+
this.setInlineTemplate(component, html);
86+
}
87+
88+
return this._injector.get(Compiler).compile(component).then((pv) => {
89+
var eventManager = this._injector.get(EventManager);
90+
var view = pv.instantiate(null, eventManager);
91+
view.hydrate(this._injector, null, null, context, null);
92+
return new ViewProxy(view);
93+
});
94+
}
95+
}
96+
97+
/**
98+
* Proxy to [View] return by [TestBed.createView] which offers a high level API for tests.
99+
*/
100+
export class ViewProxy {
101+
_view: View;
102+
103+
constructor(view: View) {
104+
this._view = view;
105+
}
106+
107+
get context(): any {
108+
return this._view.context;
109+
}
110+
111+
get nodes(): List {
112+
return this._view.nodes;
113+
}
114+
115+
detectChanges(): void {
116+
this._view.changeDetector.detectChanges();
117+
}
118+
119+
querySelector(selector) {
120+
return queryView(this._view, selector);
121+
}
122+
123+
/**
124+
* @returns {View} return the underlying [View].
125+
*
126+
* Prefer using the other methods which hide implementation details.
127+
*/
128+
get rawView(): View {
129+
return this._view;
130+
}
131+
}

modules/angular2/src/test_lib/test_injector.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
import {bind} from 'angular2/di';
2+
23
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
34
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
45
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection} from 'angular2/change_detection';
56
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
67
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
78
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
89
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
9-
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
10+
import {ShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
1011
import {XHR} from 'angular2/src/services/xhr';
11-
import {XHRMock} from 'angular2/src/mock/xhr_mock';
1212
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
1313
import {UrlResolver} from 'angular2/src/services/url_resolver';
1414
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
1515
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
16+
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
17+
18+
import {DOM} from 'angular2/src/dom/dom_adapter';
19+
20+
import {appDocumentToken} from 'angular2/src/core/application_tokens';
21+
22+
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
23+
24+
import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
25+
import {XHRMock} from 'angular2/src/mock/xhr_mock';
26+
import {MockVmTurnZone} from 'angular2/src/mock/vm_turn_zone_mock';
27+
28+
import {TestBed} from './test_bed';
1629

1730
import {Injector} from 'angular2/di';
1831

@@ -40,11 +53,23 @@ function _getRootBindings() {
4053
* @returns {*[]}
4154
*/
4255
function _getAppBindings() {
56+
var appDoc;
57+
58+
// The document is only available in browser environment
59+
try {
60+
appDoc = DOM.defaultDoc();
61+
} catch(e) {
62+
appDoc = null;
63+
}
64+
4365
return [
44-
bind(ShadowDomStrategy).toClass(NativeShadowDomStrategy),
66+
bind(appDocumentToken).toValue(appDoc),
67+
bind(ShadowDomStrategy).toFactory(
68+
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
69+
[StyleUrlResolver, appDocumentToken]),
4570
Compiler,
4671
CompilerCache,
47-
TemplateResolver,
72+
bind(TemplateResolver).toClass(MockTemplateResolver),
4873
bind(ChangeDetection).toValue(dynamicChangeDetection),
4974
TemplateLoader,
5075
DirectiveMetadataReader,
@@ -55,7 +80,15 @@ function _getAppBindings() {
5580
ComponentUrlMapper,
5681
UrlResolver,
5782
StyleUrlResolver,
58-
StyleInliner
83+
StyleInliner,
84+
TestBed,
85+
bind(VmTurnZone).toClass(MockVmTurnZone),
86+
bind(EventManager).toFactory((zone) => {
87+
var plugins = [
88+
new DomEventsPlugin(),
89+
];
90+
return new EventManager(plugins, zone);
91+
}, [VmTurnZone]),
5992
];
6093
}
6194

modules/angular2/test/core/application_spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
xdescribe,
1111
xit,
1212
} from 'angular2/test_lib';
13-
import {bootstrap, appDocumentToken, appElementToken}
14-
from 'angular2/src/core/application';
13+
import {bootstrap} from 'angular2/src/core/application';
14+
import {appDocumentToken, appElementToken} from 'angular2/src/core/application_tokens';
1515
import {Component, Decorator} from 'angular2/src/core/annotations/annotations';
1616
import {DOM} from 'angular2/src/dom/dom_adapter';
1717
import {ListWrapper} from 'angular2/src/facade/collection';

0 commit comments

Comments
 (0)