refactor(ts'ify): ts’ify mocks, directives and test_lib
Also cleans up global types.
This commit is contained in:
@ -4,16 +4,16 @@ import {NumberWrapper, BaseException, isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
var DOM = new BrowserDomAdapter();
|
||||
|
||||
export function getIntParameter(name:string) {
|
||||
export function getIntParameter(name: string) {
|
||||
return NumberWrapper.parseInt(getStringParameter(name), 10);
|
||||
}
|
||||
|
||||
export function getStringParameter(name:string) {
|
||||
var els = DOM.querySelectorAll(document, `input[name="${name}"]`)
|
||||
export function getStringParameter(name: string) {
|
||||
var els = DOM.querySelectorAll(document, `input[name="${name}"]`);
|
||||
var value;
|
||||
var el;
|
||||
|
||||
for (var i=0; i<els.length; i++) {
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
el = els[i];
|
||||
var type = DOM.type(el);
|
||||
if ((type != 'radio' && type != 'checkbox') || DOM.getChecked(el)) {
|
||||
@ -29,11 +29,9 @@ export function getStringParameter(name:string) {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function bindAction(selector:string, callback:Function) {
|
||||
export function bindAction(selector: string, callback: Function) {
|
||||
var el = DOM.querySelector(document, selector);
|
||||
DOM.on(el, 'click', function(_) {
|
||||
callback();
|
||||
});
|
||||
DOM.on(el, 'click', function(_) { callback(); });
|
||||
}
|
||||
|
||||
export function microBenchmark(name, iterationCount, callback) {
|
2
modules/angular2/src/test_lib/e2e_util.dart
Normal file
2
modules/angular2/src/test_lib/e2e_util.dart
Normal file
@ -0,0 +1,2 @@
|
||||
library angular2.e2e_util;
|
||||
// empty as this file is node.js specific and should not be transpiled to dart
|
@ -1,17 +1,16 @@
|
||||
var webdriver = require('selenium-webdriver');
|
||||
/// <reference path="../../typings/node/node.d.ts" />
|
||||
/// <reference path="../../typings/angular-protractor/angular-protractor.d.ts" />
|
||||
|
||||
module.exports = {
|
||||
verifyNoBrowserErrors: verifyNoBrowserErrors,
|
||||
clickAll: clickAll
|
||||
};
|
||||
import webdriver = require('selenium-webdriver');
|
||||
|
||||
function clickAll(buttonSelectors) {
|
||||
buttonSelectors.forEach(function(selector) {
|
||||
$(selector).click();
|
||||
});
|
||||
export var browser: protractor.IBrowser = global['browser'];
|
||||
export var $: cssSelectorHelper = global['$'];
|
||||
|
||||
export function clickAll(buttonSelectors) {
|
||||
buttonSelectors.forEach(function(selector) { $(selector).click(); });
|
||||
}
|
||||
|
||||
function verifyNoBrowserErrors() {
|
||||
export function verifyNoBrowserErrors() {
|
||||
// TODO(tbosch): Bug in ChromeDriver: Need to execute at least one command
|
||||
// so that the browser logs can be read out!
|
||||
browser.executeScript('1+1');
|
||||
@ -25,4 +24,3 @@ function verifyNoBrowserErrors() {
|
||||
expect(filteredLog.length).toEqual(0);
|
||||
});
|
||||
}
|
||||
|
@ -1,12 +1,19 @@
|
||||
/// <reference path="../../typings/jasmine/jasmine"/>
|
||||
|
||||
import {BaseException, global} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
|
||||
|
||||
var _scheduler;
|
||||
var _microtasks:List<Function> = [];
|
||||
var _microtasks: List<Function> = [];
|
||||
var _pendingPeriodicTimers: List<number> = [];
|
||||
var _pendingTimers: List<number> = [];
|
||||
var _error = null;
|
||||
|
||||
interface FakeAsyncZone extends NgZoneZone {
|
||||
_inFakeAsyncZone: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a function to be executed in the fakeAsync zone:
|
||||
* - microtasks are manually executed by calling `flushMicrotasks()`,
|
||||
@ -19,11 +26,11 @@ var _error = null;
|
||||
*/
|
||||
export function fakeAsync(fn: Function): Function {
|
||||
// TODO(vicb) re-enable once the jasmine patch from zone.js is applied
|
||||
//if (global.zone._inFakeAsyncZone) {
|
||||
// if (global.zone._inFakeAsyncZone) {
|
||||
// throw new Error('fakeAsync() calls can not be nested');
|
||||
//}
|
||||
|
||||
var fakeAsyncZone = global.zone.fork({
|
||||
var fakeAsyncZone = <FakeAsyncZone>global.zone.fork({
|
||||
setTimeout: _setTimeout,
|
||||
clearTimeout: _clearTimeout,
|
||||
setInterval: _setInterval,
|
||||
@ -32,18 +39,18 @@ export function fakeAsync(fn: Function): Function {
|
||||
_inFakeAsyncZone: true
|
||||
});
|
||||
|
||||
return function(...args) {
|
||||
_scheduler = global.jasmine.DelayedFunctionScheduler();
|
||||
return function(... args) {
|
||||
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
|
||||
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
|
||||
ListWrapper.clear(_microtasks);
|
||||
ListWrapper.clear(_pendingPeriodicTimers);
|
||||
ListWrapper.clear(_pendingTimers);
|
||||
|
||||
var res = fakeAsyncZone.run(() => {
|
||||
var res = fn(...args);
|
||||
});
|
||||
var res = fakeAsyncZone.run(() => { var res = fn(... args); });
|
||||
|
||||
if (_pendingPeriodicTimers.length > 0) {
|
||||
throw new BaseException(`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
|
||||
throw new BaseException(
|
||||
`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
|
||||
}
|
||||
|
||||
if (_pendingTimers.length > 0) {
|
||||
@ -60,7 +67,8 @@ export function fakeAsync(fn: Function): Function {
|
||||
/**
|
||||
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
|
||||
*
|
||||
* The microtasks queue is drained at the very start of this function and after any timer callback has been executed.
|
||||
* The microtasks queue is drained at the very start of this function and after any timer callback
|
||||
* has been executed.
|
||||
*
|
||||
* @param {number} millis Number of millisecond, defaults to 0
|
||||
*/
|
||||
@ -81,7 +89,7 @@ export function flushMicrotasks(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function _setTimeout(fn: Function, delay: number, ...args): number {
|
||||
function _setTimeout(fn: Function, delay: number, ... args): number {
|
||||
var cb = _fnAndFlush(fn);
|
||||
var id = _scheduler.scheduleFunction(cb, delay, args);
|
||||
ListWrapper.push(_pendingTimers, id);
|
||||
@ -94,7 +102,7 @@ function _clearTimeout(id: number) {
|
||||
return _scheduler.removeFunctionWithId(id);
|
||||
}
|
||||
|
||||
function _setInterval(fn: Function, interval: number, ...args) {
|
||||
function _setInterval(fn: Function, interval: number, ... args) {
|
||||
var cb = _fnAndFlush(fn);
|
||||
var id = _scheduler.scheduleFunction(cb, interval, args, true);
|
||||
_pendingPeriodicTimers.push(id);
|
||||
@ -106,11 +114,10 @@ function _clearInterval(id: number) {
|
||||
return _scheduler.removeFunctionWithId(id);
|
||||
}
|
||||
|
||||
function _fnAndFlush(fn: Function): void {
|
||||
return () => {
|
||||
fn.apply(global, arguments);
|
||||
flushMicrotasks();
|
||||
}
|
||||
function _fnAndFlush(fn: Function): Function {
|
||||
return (... args) => { fn.apply(global, args);
|
||||
flushMicrotasks();
|
||||
}
|
||||
}
|
||||
|
||||
function _scheduleMicrotask(microtask: Function): void {
|
||||
@ -118,14 +125,11 @@ function _scheduleMicrotask(microtask: Function): void {
|
||||
}
|
||||
|
||||
function _dequeueTimer(id: number): Function {
|
||||
return function() {
|
||||
ListWrapper.remove(_pendingTimers, id);
|
||||
}
|
||||
return function() { ListWrapper.remove(_pendingTimers, id); }
|
||||
}
|
||||
|
||||
function _assertInFakeAsyncZone(): void {
|
||||
if (!global.zone._inFakeAsyncZone) {
|
||||
if (!(<FakeAsyncZone>global.zone)._inFakeAsyncZone) {
|
||||
throw new Error('The code should be running in the fakeAsync zone to call this function');
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ export function getTypeOf(instance) {
|
||||
return instance.constructor;
|
||||
}
|
||||
|
||||
export function instantiateType(type: Function, params: Array = []) {
|
||||
export function instantiateType(type: Function, params: Array<any> = []) {
|
||||
var instance = Object.create(type.prototype);
|
||||
instance.constructor.apply(instance, params);
|
||||
return instance;
|
@ -1,4 +1,5 @@
|
||||
var testUtil = require('./e2e_util');
|
||||
|
||||
var benchpress = require('benchpress/benchpress');
|
||||
|
||||
module.exports = {
|
||||
@ -47,7 +48,7 @@ function runBenchmark(config) {
|
||||
}
|
||||
|
||||
function getScaleFactor(possibleScalings) {
|
||||
return browser.executeScript('return navigator.userAgent').then(function(userAgent) {
|
||||
return browser.executeScript('return navigator.userAgent').then(function(userAgent:string) {
|
||||
var scaleFactor = 1;
|
||||
possibleScalings.forEach(function(entry) {
|
||||
if (userAgent.match(entry.userAgent)) {
|
||||
@ -60,7 +61,7 @@ function getScaleFactor(possibleScalings) {
|
||||
|
||||
function applyScaleFactor(value, scaleFactor, method) {
|
||||
if (method === 'log2') {
|
||||
return value + Math.log2(scaleFactor);
|
||||
return value + Math.log(scaleFactor) / Math.LN2;
|
||||
} else if (method === 'sqrt') {
|
||||
return value * Math.sqrt(scaleFactor);
|
||||
} else if (method === 'linear') {
|
||||
|
2
modules/angular2/src/test_lib/perf_utils.dart
Normal file
2
modules/angular2/src/test_lib/perf_utils.dart
Normal file
@ -0,0 +1,2 @@
|
||||
library angular2.perf_util;
|
||||
// empty as this file is node.js specific and should not be transpiled to dart
|
@ -1,4 +1,4 @@
|
||||
import {Injector, bind} from 'angular2/di';
|
||||
import {Injector, bind, Injectable} from 'angular2/di';
|
||||
|
||||
import {Type, isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
@ -10,7 +10,10 @@ import {View} from 'angular2/src/core/annotations_impl/view';
|
||||
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
|
||||
import {AppView} from 'angular2/src/core/compiler/view';
|
||||
import {internalView} from 'angular2/src/core/compiler/view_ref';
|
||||
import {DynamicComponentLoader, ComponentRef} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||
import {
|
||||
DynamicComponentLoader,
|
||||
ComponentRef
|
||||
} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||
|
||||
import {queryView, viewRootNodes, el} from './utils';
|
||||
import {instantiateType, getTypeOf} from './lang_utils';
|
||||
@ -21,12 +24,11 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
/**
|
||||
* @exportedAs angular2/test
|
||||
*/
|
||||
@Injectable()
|
||||
export class TestBed {
|
||||
_injector: Injector;
|
||||
|
||||
constructor(injector: Injector) {
|
||||
this._injector = injector;
|
||||
}
|
||||
constructor(injector: Injector) { this._injector = injector; }
|
||||
|
||||
/**
|
||||
* Overrides the {@link View} of a {@link Component}.
|
||||
@ -70,11 +72,13 @@ export class TestBed {
|
||||
*
|
||||
* @param {Type} component
|
||||
* @param {*} context
|
||||
* @param {string} html Use as the component template when specified (shortcut for setInlineTemplate)
|
||||
* @param {string} html Use as the component template when specified (shortcut for
|
||||
* setInlineTemplate)
|
||||
* @return {Promise<ViewProxy>}
|
||||
*/
|
||||
createView(component: Type,
|
||||
{context = null, html = null}: {context:any, html: string} = {}): Promise<ViewProxy> {
|
||||
{context = null,
|
||||
html = null}: {context?: any, html?: string} = {}): Promise<ViewProxy> {
|
||||
if (isBlank(component) && isBlank(context)) {
|
||||
throw new BaseException('You must specified at least a component or a context');
|
||||
}
|
||||
@ -94,14 +98,15 @@ export class TestBed {
|
||||
DOM.appendChild(doc.body, rootEl);
|
||||
|
||||
var componentBinding = bind(component).toValue(context);
|
||||
return this._injector.get(DynamicComponentLoader).loadAsRoot(componentBinding,'#root', this._injector).then((hostComponentRef) => {
|
||||
return new ViewProxy(hostComponentRef);
|
||||
});
|
||||
return this._injector.get(DynamicComponentLoader)
|
||||
.loadAsRoot(componentBinding, '#root', this._injector)
|
||||
.then((hostComponentRef) => { return new ViewProxy(hostComponentRef); });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy to `AppView` return by `createView` in {@link TestBed} which offers a high level API for tests.
|
||||
* Proxy to `AppView` return by `createView` in {@link TestBed} which offers a high level API for
|
||||
* tests.
|
||||
*/
|
||||
export class ViewProxy {
|
||||
_componentRef: ComponentRef;
|
||||
@ -112,33 +117,23 @@ export class ViewProxy {
|
||||
this._view = internalView(componentRef.hostView).componentChildViews[0];
|
||||
}
|
||||
|
||||
get context(): any {
|
||||
return this._view.context;
|
||||
}
|
||||
get context(): any { return this._view.context; }
|
||||
|
||||
get rootNodes(): List {
|
||||
return viewRootNodes(this._view);
|
||||
}
|
||||
get rootNodes(): List</*node*/ any> { return viewRootNodes(this._view); }
|
||||
|
||||
detectChanges(): void {
|
||||
this._view.changeDetector.detectChanges();
|
||||
this._view.changeDetector.checkNoChanges();
|
||||
}
|
||||
|
||||
querySelector(selector) {
|
||||
return queryView(this._view, selector);
|
||||
}
|
||||
querySelector(selector) { return queryView(this._view, selector); }
|
||||
|
||||
destroy() {
|
||||
this._componentRef.dispose();
|
||||
}
|
||||
destroy() { this._componentRef.dispose(); }
|
||||
|
||||
/**
|
||||
* @returns `AppView` returns the underlying `AppView`.
|
||||
*
|
||||
* Prefer using the other methods which hide implementation details.
|
||||
*/
|
||||
get rawView(): AppView {
|
||||
return this._view;
|
||||
}
|
||||
get rawView(): AppView { return this._view; }
|
||||
}
|
@ -1,16 +1,24 @@
|
||||
import {bind} from 'angular2/di';
|
||||
import {bind, Binding} from 'angular2/di';
|
||||
|
||||
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Parser, Lexer, ChangeDetection, DynamicChangeDetection,
|
||||
PipeRegistry, defaultPipeRegistry} from 'angular2/change_detection';
|
||||
import {
|
||||
Parser,
|
||||
Lexer,
|
||||
ChangeDetection,
|
||||
DynamicChangeDetection,
|
||||
PipeRegistry,
|
||||
defaultPipeRegistry
|
||||
} from 'angular2/change_detection';
|
||||
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
||||
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
|
||||
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
|
||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||
import {EmulatedUnscopedShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
||||
import {
|
||||
EmulatedUnscopedShadowDomStrategy
|
||||
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
||||
import {XHR} from 'angular2/src/services/xhr';
|
||||
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
@ -31,7 +39,7 @@ import {TestBed} from './test_bed';
|
||||
import {Injector} from 'angular2/di';
|
||||
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {FunctionWrapper} from 'angular2/src/facade/lang';
|
||||
import {FunctionWrapper, Type} from 'angular2/src/facade/lang';
|
||||
|
||||
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
|
||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||
@ -50,7 +58,8 @@ import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
||||
*/
|
||||
function _getRootBindings() {
|
||||
return [
|
||||
bind(Reflector).toValue(reflector),
|
||||
bind(Reflector)
|
||||
.toValue(reflector),
|
||||
];
|
||||
}
|
||||
|
||||
@ -67,15 +76,17 @@ function _getAppBindings() {
|
||||
// The document is only available in browser environment
|
||||
try {
|
||||
appDoc = DOM.defaultDoc();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
appDoc = null;
|
||||
}
|
||||
|
||||
return [
|
||||
bind(DOCUMENT_TOKEN).toValue(appDoc),
|
||||
bind(ShadowDomStrategy).toFactory(
|
||||
(styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
|
||||
[StyleUrlResolver, DOCUMENT_TOKEN]),
|
||||
bind(DOCUMENT_TOKEN)
|
||||
.toValue(appDoc),
|
||||
bind(ShadowDomStrategy)
|
||||
.toFactory((styleUrlResolver, doc) =>
|
||||
new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head),
|
||||
[StyleUrlResolver, DOCUMENT_TOKEN]),
|
||||
DomRenderer,
|
||||
DefaultDomCompiler,
|
||||
bind(Renderer).toAlias(DomRenderer),
|
||||
@ -103,16 +114,19 @@ function _getAppBindings() {
|
||||
StyleInliner,
|
||||
TestBed,
|
||||
bind(NgZone).toClass(MockNgZone),
|
||||
bind(EventManager).toFactory((zone) => {
|
||||
var plugins = [
|
||||
new DomEventsPlugin(),
|
||||
];
|
||||
return new EventManager(plugins, zone);
|
||||
}, [NgZone]),
|
||||
bind(EventManager)
|
||||
.toFactory((zone) =>
|
||||
{
|
||||
var plugins = [
|
||||
new DomEventsPlugin(),
|
||||
];
|
||||
return new EventManager(plugins, zone);
|
||||
},
|
||||
[NgZone]),
|
||||
];
|
||||
}
|
||||
|
||||
export function createTestInjector(bindings: List):Injector {
|
||||
export function createTestInjector(bindings: List<Type | Binding | List<any>>): Injector {
|
||||
var rootInjector = Injector.resolveAndCreate(_getRootBindings());
|
||||
return rootInjector.resolveAndCreateChild(ListWrapper.concat(_getAppBindings(), bindings));
|
||||
}
|
||||
@ -147,22 +161,21 @@ export function createTestInjector(bindings: List):Injector {
|
||||
* @return {FunctionWithParamTokens}
|
||||
* @exportedAs angular2/test
|
||||
*/
|
||||
export function inject(tokens: List, fn: Function):FunctionWithParamTokens {
|
||||
export function inject(tokens: List<any>, fn: Function): FunctionWithParamTokens {
|
||||
return new FunctionWithParamTokens(tokens, fn);
|
||||
}
|
||||
|
||||
export class FunctionWithParamTokens {
|
||||
_tokens: List;
|
||||
_tokens: List<any>;
|
||||
_fn: Function;
|
||||
|
||||
constructor(tokens: List, fn: Function) {
|
||||
constructor(tokens: List<any>, fn: Function) {
|
||||
this._tokens = tokens;
|
||||
this._fn = fn;
|
||||
}
|
||||
|
||||
execute(injector: Injector):void {
|
||||
execute(injector: Injector): void {
|
||||
var params = ListWrapper.map(this._tokens, (t) => injector.get(t));
|
||||
FunctionWrapper.apply(this._fn, params);
|
||||
}
|
||||
}
|
||||
|
@ -1,358 +0,0 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {global} from 'angular2/src/facade/lang';
|
||||
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
|
||||
|
||||
export {inject} from './test_injector';
|
||||
|
||||
export {proxy} from 'rtts_assert/rtts_assert';
|
||||
|
||||
var _global = typeof window === 'undefined' ? global : window;
|
||||
|
||||
export var afterEach = _global.afterEach;
|
||||
export var expect = _global.expect;
|
||||
|
||||
export var IS_DARTIUM = false;
|
||||
|
||||
export class AsyncTestCompleter {
|
||||
_done: Function;
|
||||
|
||||
constructor(done: Function) {
|
||||
this._done = done;
|
||||
}
|
||||
|
||||
done() {
|
||||
this._done();
|
||||
}
|
||||
}
|
||||
|
||||
var jsmBeforeEach = _global.beforeEach;
|
||||
var jsmDescribe = _global.describe;
|
||||
var jsmDDescribe = _global.fdescribe;
|
||||
var jsmXDescribe = _global.xdescribe;
|
||||
var jsmIt = _global.it;
|
||||
var jsmIIt = _global.fit;
|
||||
var jsmXIt = _global.xit;
|
||||
|
||||
var runnerStack = [];
|
||||
var inIt = false;
|
||||
|
||||
var testBindings;
|
||||
|
||||
class BeforeEachRunner {
|
||||
constructor(parent: BeforeEachRunner) {
|
||||
this._fns = [];
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
beforeEach(fn: FunctionWithParamTokens) {
|
||||
this._fns.push(fn);
|
||||
}
|
||||
|
||||
run(injector) {
|
||||
if (this._parent) this._parent.run(injector);
|
||||
this._fns.forEach((fn) => fn.execute(injector));
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the test bindings before each test
|
||||
jsmBeforeEach(() => { testBindings = []; });
|
||||
|
||||
function _describe(jsmFn, ...args) {
|
||||
var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1];
|
||||
var runner = new BeforeEachRunner(parentRunner);
|
||||
runnerStack.push(runner);
|
||||
var suite = jsmFn(...args);
|
||||
runnerStack.pop();
|
||||
return suite;
|
||||
}
|
||||
|
||||
export function describe(...args) {
|
||||
return _describe(jsmDescribe, ...args);
|
||||
}
|
||||
|
||||
export function ddescribe(...args) {
|
||||
return _describe(jsmDDescribe, ...args);
|
||||
}
|
||||
|
||||
export function xdescribe(...args) {
|
||||
return _describe(jsmXDescribe, ...args);
|
||||
}
|
||||
|
||||
export function beforeEach(fn) {
|
||||
if (runnerStack.length > 0) {
|
||||
// Inside a describe block, beforeEach() uses a BeforeEachRunner
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
runner.beforeEach(fn);
|
||||
} else {
|
||||
// Top level beforeEach() are delegated to jasmine
|
||||
jsmBeforeEach(fn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding default bindings defined in test_injector.js.
|
||||
*
|
||||
* The given function must return a list of DI bindings.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* beforeEachBindings(() => [
|
||||
* bind(Compiler).toClass(MockCompiler),
|
||||
* bind(SomeToken).toValue(myValue),
|
||||
* ]);
|
||||
*/
|
||||
export function beforeEachBindings(fn) {
|
||||
jsmBeforeEach(() => {
|
||||
var bindings = fn();
|
||||
if (!bindings) return;
|
||||
testBindings = [...testBindings, ...bindings];
|
||||
});
|
||||
}
|
||||
|
||||
function _it(jsmFn, name, fn) {
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
|
||||
jsmFn(name, function(done) {
|
||||
var async = false;
|
||||
|
||||
var completerBinding = bind(AsyncTestCompleter).toFactory(() => {
|
||||
// Mark the test as async when an AsyncTestCompleter is injected in an it()
|
||||
if (!inIt) throw new Error('AsyncTestCompleter can only be injected in an "it()"');
|
||||
async = true;
|
||||
return new AsyncTestCompleter(done);
|
||||
});
|
||||
|
||||
var injector = createTestInjector([...testBindings, completerBinding]);
|
||||
runner.run(injector);
|
||||
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
|
||||
inIt = true;
|
||||
fn.execute(injector);
|
||||
inIt = false;
|
||||
|
||||
if (!async) done();
|
||||
});
|
||||
}
|
||||
|
||||
export function it(name, fn) {
|
||||
return _it(jsmIt, name, fn);
|
||||
}
|
||||
|
||||
export function xit(name, fn) {
|
||||
return _it(jsmXIt, name, fn);
|
||||
}
|
||||
|
||||
export function iit(name, fn) {
|
||||
return _it(jsmIIt, name, fn);
|
||||
}
|
||||
|
||||
// To make testing consistent between dart and js
|
||||
_global.print = function(msg) {
|
||||
if (_global.dump) {
|
||||
_global.dump(msg);
|
||||
} else {
|
||||
_global.console.log(msg);
|
||||
}
|
||||
};
|
||||
|
||||
// Some Map polyfills don't polyfill Map.toString correctly, which
|
||||
// gives us bad error messages in tests.
|
||||
// The only way to do this in Jasmine is to monkey patch a method
|
||||
// to the object :-(
|
||||
_global.Map.prototype.jasmineToString = function() {
|
||||
var m = this;
|
||||
if (!m) {
|
||||
return ''+m;
|
||||
}
|
||||
var res = [];
|
||||
m.forEach( (v,k) => {
|
||||
res.push(`${k}:${v}`);
|
||||
});
|
||||
return `{ ${res.join(',')} }`;
|
||||
}
|
||||
|
||||
_global.beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
// Custom handler for Map as Jasmine does not support it yet
|
||||
toEqual: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
return {
|
||||
pass: util.equals(actual, expected, [compareMap])
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function compareMap(actual, expected) {
|
||||
if (actual instanceof Map) {
|
||||
var pass = actual.size === expected.size;
|
||||
if (pass) {
|
||||
actual.forEach( (v,k) => {
|
||||
pass = pass && util.equals(v, expected.get(k));
|
||||
});
|
||||
}
|
||||
return pass;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toBePromise: function() {
|
||||
return {
|
||||
compare: function (actual, expectedClass) {
|
||||
var pass = typeof actual === 'object' && typeof actual.then === 'function';
|
||||
return {
|
||||
pass: pass,
|
||||
get message() {
|
||||
return 'Expected ' + actual + ' to be a promise';
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
toBeAnInstanceOf: function() {
|
||||
return {
|
||||
compare: function(actual, expectedClass) {
|
||||
var pass = typeof actual === 'object' && actual instanceof expectedClass;
|
||||
return {
|
||||
pass: pass,
|
||||
get message() {
|
||||
return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
toHaveText: function() {
|
||||
return {
|
||||
compare: function(actual, expectedText) {
|
||||
var actualText = elementText(actual);
|
||||
return {
|
||||
pass: actualText == expectedText,
|
||||
get message() {
|
||||
return 'Expected ' + actualText + ' to be equal to ' + expectedText;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
toImplement: function() {
|
||||
return {
|
||||
compare: function(actualObject, expectedInterface) {
|
||||
var objProps = Object.keys(actualObject.constructor.prototype);
|
||||
var intProps = Object.keys(expectedInterface.prototype);
|
||||
|
||||
var missedMethods = [];
|
||||
intProps.forEach((k) => {
|
||||
if (!actualObject.constructor.prototype[k]) missedMethods.push(k);
|
||||
});
|
||||
|
||||
return {
|
||||
pass: missedMethods.length == 0,
|
||||
get message() {
|
||||
return 'Expected ' + actualObject + ' to have the following methods: ' + missedMethods.join(", ");
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export class SpyObject {
|
||||
constructor(type = null) {
|
||||
if (type) {
|
||||
for (var prop in type.prototype) {
|
||||
var m = null;
|
||||
try {
|
||||
m = type.prototype[prop];
|
||||
} catch (e) {
|
||||
// As we are creating spys for abstract classes,
|
||||
// these classes might have getters that throw when they are accessed.
|
||||
// As we are only auto creating spys for methods, this
|
||||
// should not matter.
|
||||
}
|
||||
if (typeof m === 'function') {
|
||||
this.spy(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spy(name){
|
||||
if (! this[name]) {
|
||||
this[name] = this._createGuinnessCompatibleSpy();
|
||||
}
|
||||
return this[name];
|
||||
}
|
||||
|
||||
static stub(object = null, config = null, overrides = null) {
|
||||
if (!(object instanceof 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;
|
||||
}
|
||||
|
||||
rttsAssert(value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_createGuinnessCompatibleSpy(){
|
||||
var newSpy = jasmine.createSpy();
|
||||
newSpy.andCallFake = newSpy.and.callFake;
|
||||
newSpy.andReturn = newSpy.and.returnValue;
|
||||
// return null by default to satisfy our rtts asserts
|
||||
newSpy.and.returnValue(null);
|
||||
return newSpy;
|
||||
}
|
||||
}
|
||||
|
||||
function elementText(n) {
|
||||
var hasNodes = (n) => {var children = DOM.childNodes(n); return children && children.length > 0;}
|
||||
|
||||
if (n instanceof Array) {
|
||||
return n.map((nn) => elementText(nn)).join("");
|
||||
}
|
||||
|
||||
if (DOM.isCommentNode(n)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
||||
return elementText(Array.prototype.slice.apply(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);
|
||||
}
|
||||
|
||||
export function isInInnerZone(): boolean {
|
||||
return global.zone._innerZone === true;
|
||||
}
|
352
modules/angular2/src/test_lib/test_lib.ts
Normal file
352
modules/angular2/src/test_lib/test_lib.ts
Normal file
@ -0,0 +1,352 @@
|
||||
/// <reference path="../../typings/jasmine/jasmine"/>
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {global} from 'angular2/src/facade/lang';
|
||||
import {NgZoneZone} from 'angular2/src/core/zone/ng_zone';
|
||||
|
||||
import {bind} from 'angular2/di';
|
||||
|
||||
import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector';
|
||||
|
||||
export {inject} from './test_injector';
|
||||
|
||||
export function proxy() {
|
||||
}
|
||||
|
||||
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
|
||||
|
||||
export var afterEach = _global.afterEach;
|
||||
export var expect = _global.expect;
|
||||
|
||||
export var IS_DARTIUM = false;
|
||||
|
||||
export class AsyncTestCompleter {
|
||||
_done: Function;
|
||||
|
||||
constructor(done: Function) { this._done = done; }
|
||||
|
||||
done() { this._done(); }
|
||||
}
|
||||
|
||||
var jsmBeforeEach = _global.beforeEach;
|
||||
var jsmDescribe = _global.describe;
|
||||
var jsmDDescribe = _global.fdescribe;
|
||||
var jsmXDescribe = _global.xdescribe;
|
||||
var jsmIt = _global.it;
|
||||
var jsmIIt = _global.fit;
|
||||
var jsmXIt = _global.xit;
|
||||
|
||||
var runnerStack = [];
|
||||
var inIt = false;
|
||||
|
||||
var testBindings;
|
||||
|
||||
class BeforeEachRunner {
|
||||
_fns: List<FunctionWithParamTokens>;
|
||||
_parent: BeforeEachRunner;
|
||||
constructor(parent: BeforeEachRunner) {
|
||||
this._fns = [];
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
beforeEach(fn: FunctionWithParamTokens) { this._fns.push(fn); }
|
||||
|
||||
run(injector) {
|
||||
if (this._parent) this._parent.run(injector);
|
||||
this._fns.forEach((fn) => fn.execute(injector));
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the test bindings before each test
|
||||
jsmBeforeEach(() => { testBindings = []; });
|
||||
|
||||
function _describe(jsmFn, ... args) {
|
||||
var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1];
|
||||
var runner = new BeforeEachRunner(parentRunner);
|
||||
runnerStack.push(runner);
|
||||
var suite = jsmFn(... args);
|
||||
runnerStack.pop();
|
||||
return suite;
|
||||
}
|
||||
|
||||
export function describe(... args) {
|
||||
return _describe(jsmDescribe, ... args);
|
||||
}
|
||||
|
||||
export function ddescribe(... args) {
|
||||
return _describe(jsmDDescribe, ... args);
|
||||
}
|
||||
|
||||
export function xdescribe(... args) {
|
||||
return _describe(jsmXDescribe, ... args);
|
||||
}
|
||||
|
||||
export function beforeEach(fn) {
|
||||
if (runnerStack.length > 0) {
|
||||
// Inside a describe block, beforeEach() uses a BeforeEachRunner
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
runner.beforeEach(fn);
|
||||
} else {
|
||||
// Top level beforeEach() are delegated to jasmine
|
||||
jsmBeforeEach(fn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding default bindings defined in test_injector.js.
|
||||
*
|
||||
* The given function must return a list of DI bindings.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* beforeEachBindings(() => [
|
||||
* bind(Compiler).toClass(MockCompiler),
|
||||
* bind(SomeToken).toValue(myValue),
|
||||
* ]);
|
||||
*/
|
||||
export function beforeEachBindings(fn) {
|
||||
jsmBeforeEach(() => {
|
||||
var bindings = fn();
|
||||
if (!bindings) return;
|
||||
testBindings = [... testBindings, ... bindings];
|
||||
});
|
||||
}
|
||||
|
||||
function _it(jsmFn, name, fn) {
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
|
||||
jsmFn(name, function(done) {
|
||||
var async = false;
|
||||
|
||||
var completerBinding =
|
||||
bind(AsyncTestCompleter)
|
||||
.toFactory(() => {
|
||||
// Mark the test as async when an AsyncTestCompleter is injected in an it()
|
||||
if (!inIt) throw new Error('AsyncTestCompleter can only be injected in an "it()"');
|
||||
async = true;
|
||||
return new AsyncTestCompleter(done);
|
||||
});
|
||||
|
||||
var injector = createTestInjector([... testBindings, completerBinding]);
|
||||
runner.run(injector);
|
||||
|
||||
if (!(fn instanceof FunctionWithParamTokens)) {
|
||||
fn = inject([], fn);
|
||||
}
|
||||
|
||||
inIt = true;
|
||||
fn.execute(injector);
|
||||
inIt = false;
|
||||
|
||||
if (!async) done();
|
||||
});
|
||||
}
|
||||
|
||||
export function it(name, fn) {
|
||||
return _it(jsmIt, name, fn);
|
||||
}
|
||||
|
||||
export function xit(name, fn) {
|
||||
return _it(jsmXIt, name, fn);
|
||||
}
|
||||
|
||||
export function iit(name, fn) {
|
||||
return _it(jsmIIt, name, fn);
|
||||
}
|
||||
|
||||
// Some Map polyfills don't polyfill Map.toString correctly, which
|
||||
// gives us bad error messages in tests.
|
||||
// The only way to do this in Jasmine is to monkey patch a method
|
||||
// to the object :-(
|
||||
Map.prototype['jasmineToString'] =
|
||||
function() {
|
||||
var m = this;
|
||||
if (!m) {
|
||||
return '' + m;
|
||||
}
|
||||
var res = [];
|
||||
m.forEach((v, k) => { res.push(`${k}:${v}`); });
|
||||
return `{ ${res.join(',')} }`;
|
||||
}
|
||||
|
||||
_global.beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
// Custom handler for Map as Jasmine does not support it yet
|
||||
toEqual: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
return {pass: util.equals(actual, expected, [compareMap])};
|
||||
}
|
||||
};
|
||||
|
||||
function compareMap(actual, expected) {
|
||||
if (actual instanceof Map) {
|
||||
var pass = actual.size === expected.size;
|
||||
if (pass) {
|
||||
actual.forEach((v, k) => { pass = pass && util.equals(v, expected.get(k)); });
|
||||
}
|
||||
return pass;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toBePromise: function() {
|
||||
return {
|
||||
compare: function(actual, expectedClass) {
|
||||
var pass = typeof actual === 'object' && typeof actual.then === 'function';
|
||||
return {
|
||||
pass: pass,
|
||||
get message() { return 'Expected ' + actual + ' to be a promise'; }
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
toBeAnInstanceOf: function() {
|
||||
return {
|
||||
compare: function(actual, expectedClass) {
|
||||
var pass = typeof actual === 'object' && actual instanceof expectedClass;
|
||||
return {
|
||||
pass: pass,
|
||||
get message() {
|
||||
return 'Expected ' + actual + ' to be an instance of ' + expectedClass;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
toHaveText: function() {
|
||||
return {
|
||||
compare: function(actual, expectedText) {
|
||||
var actualText = elementText(actual);
|
||||
return {
|
||||
pass: actualText == expectedText,
|
||||
get message() {
|
||||
return 'Expected ' + actualText + ' to be equal to ' + expectedText;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
toImplement: function() {
|
||||
return {
|
||||
compare: function(actualObject, expectedInterface) {
|
||||
var objProps = Object.keys(actualObject.constructor.prototype);
|
||||
var intProps = Object.keys(expectedInterface.prototype);
|
||||
|
||||
var missedMethods = [];
|
||||
intProps.forEach((k) => {
|
||||
if (!actualObject.constructor.prototype[k]) missedMethods.push(k);
|
||||
});
|
||||
|
||||
return {
|
||||
pass: missedMethods.length == 0,
|
||||
get message() {
|
||||
return 'Expected ' + actualObject + ' to have the following methods: ' +
|
||||
missedMethods.join(", ");
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export interface GuinessCompatibleSpy extends jasmine.Spy {
|
||||
/** By chaining the spy with and.returnValue, all calls to the function will return a specific
|
||||
* value. */
|
||||
andReturn(val: any): void;
|
||||
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
|
||||
* function. */
|
||||
andCallFake(fn: Function): GuinessCompatibleSpy;
|
||||
}
|
||||
|
||||
export class SpyObject {
|
||||
constructor(type = null) {
|
||||
if (type) {
|
||||
for (var prop in type.prototype) {
|
||||
var m = null;
|
||||
try {
|
||||
m = type.prototype[prop];
|
||||
} catch (e) {
|
||||
// As we are creating spys for abstract classes,
|
||||
// these classes might have getters that throw when they are accessed.
|
||||
// As we are only auto creating spys for methods, this
|
||||
// should not matter.
|
||||
}
|
||||
if (typeof m === 'function') {
|
||||
this.spy(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
spy(name) {
|
||||
if (!this[name]) {
|
||||
this[name] = this._createGuinnessCompatibleSpy(name);
|
||||
}
|
||||
return this[name];
|
||||
}
|
||||
|
||||
static stub(object = null, config = null, overrides = null) {
|
||||
if (!(object instanceof 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;
|
||||
}
|
||||
|
||||
rttsAssert(value) { return true; }
|
||||
|
||||
_createGuinnessCompatibleSpy(name): GuinessCompatibleSpy {
|
||||
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
|
||||
newSpy.andCallFake = <any>newSpy.and.callFake;
|
||||
newSpy.andReturn = <any>newSpy.and.returnValue;
|
||||
// return null by default to satisfy our rtts asserts
|
||||
newSpy.and.returnValue(null);
|
||||
return newSpy;
|
||||
}
|
||||
}
|
||||
|
||||
function elementText(n) {
|
||||
var hasNodes = (n) => { var children = DOM.childNodes(n);
|
||||
return children && children.length > 0;
|
||||
}
|
||||
|
||||
if (n instanceof Array) {
|
||||
return n.map((nn) => elementText(nn)).join("");
|
||||
}
|
||||
|
||||
if (DOM.isCommentNode(n)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (DOM.isElementNode(n) && DOM.tagName(n) == 'CONTENT') {
|
||||
return elementText(Array.prototype.slice.apply(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);
|
||||
}
|
||||
|
||||
export function isInInnerZone(): boolean {
|
||||
return (<NgZoneZone>global.zone)._innerZone === true;
|
||||
}
|
@ -4,32 +4,28 @@ import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {resolveInternalDomView} from 'angular2/src/render/dom/view/view';
|
||||
|
||||
export class Log {
|
||||
_result:List;
|
||||
_result: List<any>;
|
||||
|
||||
constructor() {
|
||||
this._result = [];
|
||||
}
|
||||
constructor() { this._result = []; }
|
||||
|
||||
add(value):void {
|
||||
ListWrapper.push(this._result, value);
|
||||
}
|
||||
add(value): void { ListWrapper.push(this._result, value); }
|
||||
|
||||
fn(value) {
|
||||
return (a1 = null, a2 = null, a3 = null, a4 = null, a5 = null) => {
|
||||
ListWrapper.push(this._result, value);
|
||||
}
|
||||
}
|
||||
|
||||
result():string {
|
||||
return ListWrapper.join(this._result, "; ");
|
||||
}
|
||||
}
|
||||
|
||||
export function viewRootNodes(view):List {
|
||||
result(): string {
|
||||
return ListWrapper.join(this._result, "; ");
|
||||
}
|
||||
}
|
||||
|
||||
export function viewRootNodes(view): List</*node*/ any> {
|
||||
return resolveInternalDomView(view.render).rootNodes;
|
||||
}
|
||||
|
||||
export function queryView(view, selector:string) {
|
||||
export function queryView(view, selector: string) {
|
||||
var rootNodes = viewRootNodes(view);
|
||||
for (var i = 0; i < rootNodes.length; ++i) {
|
||||
var res = DOM.querySelector(rootNodes[i], selector);
|
||||
@ -44,6 +40,6 @@ export function dispatchEvent(element, eventType) {
|
||||
DOM.dispatchEvent(element, DOM.createEvent(eventType));
|
||||
}
|
||||
|
||||
export function el(html:string) {
|
||||
export function el(html: string) {
|
||||
return DOM.firstChild(DOM.content(DOM.createTemplate(html)));
|
||||
}
|
Reference in New Issue
Block a user