refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
@ -0,0 +1,313 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AnimationPlayer, AnimationTriggerMetadata, animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {Component, Injectable, RendererFactory2, RendererType2, ViewChild} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BrowserAnimationsModule, ɵAnimationEngine, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
|
||||
|
||||
import {InjectableAnimationEngine} from '../../animations/src/providers';
|
||||
import {el} from '../../testing/browser_util';
|
||||
|
||||
export function main() {
|
||||
describe('ɵAnimationRenderer', () => {
|
||||
let element: any;
|
||||
beforeEach(() => {
|
||||
element = el('<div></div>');
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: ɵAnimationEngine, useClass: MockAnimationEngine}],
|
||||
imports: [BrowserAnimationsModule]
|
||||
});
|
||||
});
|
||||
|
||||
function makeRenderer(animationTriggers: any[] = []) {
|
||||
const type = <RendererType2>{
|
||||
id: 'id',
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
data: {'animation': animationTriggers}
|
||||
};
|
||||
return (TestBed.get(RendererFactory2) as ɵAnimationRendererFactory)
|
||||
.createRenderer(element, type);
|
||||
}
|
||||
|
||||
it('should register the provided triggers with the view engine when created', () => {
|
||||
const renderer = makeRenderer([trigger('trig1', []), trigger('trig2', [])]);
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
expect(engine.triggers.map(t => t.name)).toEqual(['trig1', 'trig2']);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when appending children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.appendChild(container, element);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when inserting a child before another',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
const element2 = el('<div></div>');
|
||||
container.appendChild(element2);
|
||||
|
||||
renderer.insertBefore(container, element, element2);
|
||||
expect(engine.captures['onInsert'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s insert operations when removing children', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
const container = el('<div></div>');
|
||||
|
||||
renderer.removeChild(container, element);
|
||||
expect(engine.captures['onRemove'].pop()).toEqual([element]);
|
||||
});
|
||||
|
||||
it('should hook into the engine\'s setProperty call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
|
||||
renderer.setProperty(element, 'prop', 'value');
|
||||
expect(engine.captures['setProperty']).toBeFalsy();
|
||||
|
||||
renderer.setProperty(element, '@prop', 'value');
|
||||
expect(engine.captures['setProperty'].pop()).toEqual([element, 'id#prop', 'value']);
|
||||
});
|
||||
|
||||
describe('listen', () => {
|
||||
it('should hook into the engine\'s listen call if the property begins with `@`', () => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => { return true; };
|
||||
|
||||
renderer.listen(element, 'event', cb);
|
||||
expect(engine.captures['listen']).toBeFalsy();
|
||||
|
||||
renderer.listen(element, '@event.phase', cb);
|
||||
expect(engine.captures['listen'].pop()).toEqual([element, 'id#event', 'phase']);
|
||||
});
|
||||
|
||||
it('should resolve the body|document|window nodes given their values as strings as input',
|
||||
() => {
|
||||
const renderer = makeRenderer();
|
||||
const engine = TestBed.get(ɵAnimationEngine) as MockAnimationEngine;
|
||||
|
||||
const cb = (event: any): boolean => { return true; };
|
||||
|
||||
renderer.listen('body', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document.body);
|
||||
|
||||
renderer.listen('document', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(document);
|
||||
|
||||
renderer.listen('window', '@event', cb);
|
||||
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flushing animations', () => {
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (typeof Element == 'undefined') return;
|
||||
|
||||
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)"></div>',
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
'* => state',
|
||||
[style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
event: any;
|
||||
onStart(event: any) { this.event = event; }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: ɵAnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = 'state';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(cmp.event.triggerName).toEqual('myAnimation');
|
||||
expect(cmp.event.phaseName).toEqual('start');
|
||||
cmp.event = null;
|
||||
|
||||
engine.flush();
|
||||
expect(cmp.event).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly insert/remove nodes through the animation renderer that do not contain animations',
|
||||
(async) => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: '<div #elm *ngIf="exp"></div>',
|
||||
animations: [trigger(
|
||||
'someAnimation',
|
||||
[transition(
|
||||
'* => *', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any;
|
||||
@ViewChild('elm') public element: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: ɵAnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
cmp.exp = false;
|
||||
const element = cmp.element;
|
||||
expect(element.nativeElement.parentNode).toBeTruthy();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.nativeElement.parentNode).toBeFalsy();
|
||||
async();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should only queue up dom removals if the element itself contains a valid leave animation',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div #elm1 *ngIf="exp1"></div>
|
||||
<div #elm2 @animation1 *ngIf="exp2"></div>
|
||||
<div #elm3 @animation2 *ngIf="exp3"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('animation1', [transition('a => b', [])]),
|
||||
trigger('animation2', [transition(':leave', [])]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp1: any = true;
|
||||
exp2: any = true;
|
||||
exp3: any = true;
|
||||
|
||||
@ViewChild('elm1') public elm1: any;
|
||||
|
||||
@ViewChild('elm2') public elm2: any;
|
||||
|
||||
@ViewChild('elm3') public elm3: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{provide: ɵAnimationEngine, useClass: InjectableAnimationEngine}],
|
||||
declarations: [Cmp]
|
||||
});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
const elm1 = cmp.elm1;
|
||||
const elm2 = cmp.elm2;
|
||||
const elm3 = cmp.elm3;
|
||||
assertHasParent(elm1);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
finishPlayers(engine.activePlayers);
|
||||
|
||||
cmp.exp1 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.activePlayers.length).toEqual(0);
|
||||
|
||||
cmp.exp2 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.activePlayers.length).toEqual(0);
|
||||
|
||||
cmp.exp3 = false;
|
||||
fixture.detectChanges();
|
||||
assertHasParent(elm1, false);
|
||||
assertHasParent(elm2, false);
|
||||
assertHasParent(elm3);
|
||||
engine.flush();
|
||||
expect(engine.activePlayers.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class MockAnimationEngine extends ɵAnimationEngine {
|
||||
captures: {[method: string]: any[]} = {};
|
||||
triggers: AnimationTriggerMetadata[] = [];
|
||||
|
||||
private _capture(name: string, args: any[]) {
|
||||
const data = this.captures[name] = this.captures[name] || [];
|
||||
data.push(args);
|
||||
}
|
||||
|
||||
registerTrigger(trigger: AnimationTriggerMetadata) { this.triggers.push(trigger); }
|
||||
|
||||
onInsert(element: any, domFn: () => any): void { this._capture('onInsert', [element]); }
|
||||
|
||||
onRemove(element: any, domFn: () => any): void { this._capture('onRemove', [element]); }
|
||||
|
||||
setProperty(element: any, property: string, value: any): void {
|
||||
this._capture('setProperty', [element, property, value]);
|
||||
}
|
||||
|
||||
listen(element: any, eventName: string, eventPhase: string, callback: (event: any) => any):
|
||||
() => void {
|
||||
// we don't capture the callback here since the renderer wraps it in a zone
|
||||
this._capture('listen', [element, eventName, eventPhase]);
|
||||
return () => {};
|
||||
}
|
||||
|
||||
flush() {}
|
||||
}
|
||||
|
||||
|
||||
function assertHasParent(element: any, yes: boolean = true) {
|
||||
const parent = element.nativeElement.parentNode;
|
||||
if (yes) {
|
||||
expect(parent).toBeTruthy();
|
||||
} else {
|
||||
expect(parent).toBeFalsy();
|
||||
}
|
||||
}
|
||||
|
||||
function finishPlayers(players: AnimationPlayer[]) {
|
||||
players.forEach(player => player.finish());
|
||||
}
|
410
packages/platform-browser/test/browser/bootstrap_spec.ts
Normal file
410
packages/platform-browser/test/browser/bootstrap_spec.ts
Normal file
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isPlatformBrowser} from '@angular/common';
|
||||
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, VERSION, createPlatformFactory, ɵstringify as stringify} from '@angular/core';
|
||||
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
|
||||
import {Console} from '@angular/core/src/console';
|
||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
|
||||
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
@Component({selector: 'non-existent', template: ''})
|
||||
class NonExistentComp {
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: '{{greeting}} world!'})
|
||||
class HelloRootCmp {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: 'before: <ng-content></ng-content> after: done'})
|
||||
class HelloRootCmpContent {
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app-2', template: '{{greeting}} world, again!'})
|
||||
class HelloRootCmp2 {
|
||||
greeting: string;
|
||||
constructor() { this.greeting = 'hello'; }
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: ''})
|
||||
class HelloRootCmp3 {
|
||||
appBinding: any /** TODO #9100 */;
|
||||
|
||||
constructor(@Inject('appBinding') appBinding: any /** TODO #9100 */) {
|
||||
this.appBinding = appBinding;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: ''})
|
||||
class HelloRootCmp4 {
|
||||
appRef: any /** TODO #9100 */;
|
||||
|
||||
constructor(@Inject(ApplicationRef) appRef: ApplicationRef) { this.appRef = appRef; }
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app'})
|
||||
class HelloRootMissingTemplate {
|
||||
}
|
||||
|
||||
@Directive({selector: 'hello-app'})
|
||||
class HelloRootDirectiveIsNotCmp {
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: ''})
|
||||
class HelloOnDestroyTickCmp implements OnDestroy {
|
||||
appRef: ApplicationRef;
|
||||
constructor(@Inject(ApplicationRef) appRef: ApplicationRef) { this.appRef = appRef; }
|
||||
|
||||
ngOnDestroy(): void { this.appRef.tick(); }
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', templateUrl: './sometemplate.html'})
|
||||
class HelloUrlCmp {
|
||||
greeting = 'hello';
|
||||
}
|
||||
|
||||
@Directive({selector: '[someDir]', host: {'[title]': 'someDir'}})
|
||||
class SomeDirective {
|
||||
@Input()
|
||||
someDir: string;
|
||||
}
|
||||
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
transform(value: string): any { return `transformed ${value}`; }
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: `<div [someDir]="'someValue' | somePipe"></div>`})
|
||||
class HelloCmpUsingPlatformDirectiveAndPipe {
|
||||
show: boolean = false;
|
||||
}
|
||||
|
||||
@Component({selector: 'hello-app', template: '<some-el [someProp]="true">hello world!</some-el>'})
|
||||
class HelloCmpUsingCustomElement {
|
||||
}
|
||||
|
||||
class MockConsole {
|
||||
res: any[] = [];
|
||||
error(s: any): void { this.res.push(s); }
|
||||
}
|
||||
|
||||
|
||||
class DummyConsole implements Console {
|
||||
public warnings: string[] = [];
|
||||
|
||||
log(message: string) {}
|
||||
warn(message: string) { this.warnings.push(message); }
|
||||
}
|
||||
|
||||
|
||||
class TestModule {}
|
||||
function bootstrap(
|
||||
cmpType: any, providers: Provider[] = [], platformProviders: Provider[] = []): Promise<any> {
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
declarations: [cmpType],
|
||||
bootstrap: [cmpType],
|
||||
providers: providers,
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
class TestModule {
|
||||
}
|
||||
return platformBrowserDynamic(platformProviders).bootstrapModule(TestModule);
|
||||
}
|
||||
|
||||
export function main() {
|
||||
let el: any /** TODO #9100 */, el2: any /** TODO #9100 */, testProviders: Provider[],
|
||||
lightDom: any /** TODO #9100 */;
|
||||
|
||||
describe('bootstrap factory method', () => {
|
||||
let compilerConsole: DummyConsole;
|
||||
|
||||
beforeEachProviders(() => { return [Log]; });
|
||||
|
||||
beforeEach(inject([DOCUMENT], (doc: any) => {
|
||||
destroyPlatform();
|
||||
compilerConsole = new DummyConsole();
|
||||
testProviders = [{provide: Console, useValue: compilerConsole}];
|
||||
|
||||
const oldRoots = getDOM().querySelectorAll(doc, 'hello-app,hello-app-2,light-dom-el');
|
||||
for (let i = 0; i < oldRoots.length; i++) {
|
||||
getDOM().remove(oldRoots[i]);
|
||||
}
|
||||
|
||||
el = getDOM().createElement('hello-app', doc);
|
||||
el2 = getDOM().createElement('hello-app-2', doc);
|
||||
lightDom = getDOM().createElement('light-dom-el', doc);
|
||||
getDOM().appendChild(doc.body, el);
|
||||
getDOM().appendChild(doc.body, el2);
|
||||
getDOM().appendChild(el, lightDom);
|
||||
getDOM().setText(lightDom, 'loading');
|
||||
}));
|
||||
|
||||
afterEach(destroyPlatform);
|
||||
|
||||
it('should throw if bootstrapped Directive is not a Component',
|
||||
inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
|
||||
const logger = new MockConsole();
|
||||
const errorHandler = new ErrorHandler(false);
|
||||
errorHandler._console = logger as any;
|
||||
expect(
|
||||
() => bootstrap(
|
||||
HelloRootDirectiveIsNotCmp, [{provide: ErrorHandler, useValue: errorHandler}]))
|
||||
.toThrowError(`HelloRootDirectiveIsNotCmp cannot be used as an entry component.`);
|
||||
done.done();
|
||||
}));
|
||||
|
||||
it('should throw if no element is found',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const logger = new MockConsole();
|
||||
const errorHandler = new ErrorHandler(false);
|
||||
errorHandler._console = logger as any;
|
||||
bootstrap(NonExistentComp, [
|
||||
{provide: ErrorHandler, useValue: errorHandler}
|
||||
]).then(null, (reason) => {
|
||||
expect(reason.message)
|
||||
.toContain('The selector "non-existent" did not match any elements');
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
it('should forward the error to promise when bootstrap fails',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const logger = new MockConsole();
|
||||
const errorHandler = new ErrorHandler(false);
|
||||
errorHandler._console = logger as any;
|
||||
|
||||
const refPromise =
|
||||
bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]);
|
||||
refPromise.then(null, (reason: any) => {
|
||||
expect(reason.message)
|
||||
.toContain('The selector "non-existent" did not match any elements');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should invoke the default exception handler when bootstrap fails',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const logger = new MockConsole();
|
||||
const errorHandler = new ErrorHandler(false);
|
||||
errorHandler._console = logger as any;
|
||||
|
||||
const refPromise =
|
||||
bootstrap(NonExistentComp, [{provide: ErrorHandler, useValue: errorHandler}]);
|
||||
refPromise.then(null, (reason) => {
|
||||
expect(logger.res.join(''))
|
||||
.toContain('The selector "non-existent" did not match any elements');
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
it('should create an injector promise', () => {
|
||||
const refPromise = bootstrap(HelloRootCmp, testProviders);
|
||||
expect(refPromise).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should set platform name to browser',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise = bootstrap(HelloRootCmp, testProviders);
|
||||
refPromise.then((ref) => {
|
||||
expect(isPlatformBrowser(ref.injector.get(PLATFORM_ID))).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display hello world', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise = bootstrap(HelloRootCmp, testProviders);
|
||||
refPromise.then((ref) => {
|
||||
expect(el).toHaveText('hello world!');
|
||||
expect(el.getAttribute('ng-version')).toEqual(VERSION.full);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw a descriptive error if BrowserModule is installed again via a lazily loaded module',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
@NgModule({imports: [BrowserModule]})
|
||||
class AsyncModule {
|
||||
}
|
||||
bootstrap(HelloRootCmp, testProviders)
|
||||
.then((ref: ComponentRef<HelloRootCmp>) => {
|
||||
const compiler: Compiler = ref.injector.get(Compiler);
|
||||
return compiler.compileModuleAsync(AsyncModule).then(factory => {
|
||||
expect(() => factory.create(ref.injector))
|
||||
.toThrowError(
|
||||
`BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.`);
|
||||
});
|
||||
})
|
||||
.then(() => async.done(), err => async.fail(err));
|
||||
}));
|
||||
|
||||
it('should support multiple calls to bootstrap',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise1 = bootstrap(HelloRootCmp, testProviders);
|
||||
const refPromise2 = bootstrap(HelloRootCmp2, testProviders);
|
||||
Promise.all([refPromise1, refPromise2]).then((refs) => {
|
||||
expect(el).toHaveText('hello world!');
|
||||
expect(el2).toHaveText('hello world, again!');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not crash if change detection is invoked when the root component is disposed',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
bootstrap(HelloOnDestroyTickCmp, testProviders).then((ref) => {
|
||||
expect(() => ref.destroy()).not.toThrow();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should unregister change detectors when components are disposed',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
bootstrap(HelloRootCmp, testProviders).then((ref) => {
|
||||
const appRef = ref.injector.get(ApplicationRef);
|
||||
ref.destroy();
|
||||
expect(() => appRef.tick()).not.toThrow();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should make the provided bindings available to the application component',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise = bootstrap(
|
||||
HelloRootCmp3, [testProviders, {provide: 'appBinding', useValue: 'BoundValue'}]);
|
||||
|
||||
refPromise.then((ref) => {
|
||||
expect(ref.injector.get('appBinding')).toEqual('BoundValue');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not override locale provided during bootstrap',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise =
|
||||
bootstrap(HelloRootCmp, [testProviders], [{provide: LOCALE_ID, useValue: 'fr-FR'}]);
|
||||
|
||||
refPromise.then(ref => {
|
||||
expect(ref.injector.get(LOCALE_ID)).toEqual('fr-FR');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should avoid cyclic dependencies when root component requires Lifecycle through DI',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise = bootstrap(HelloRootCmp4, testProviders);
|
||||
|
||||
refPromise.then((ref) => {
|
||||
const appRef = ref.injector.get(ApplicationRef);
|
||||
expect(appRef).toBeDefined();
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should run platform initializers',
|
||||
inject([Log, AsyncTestCompleter], (log: Log, async: AsyncTestCompleter) => {
|
||||
const p = createPlatformFactory(platformBrowserDynamic, 'someName', [
|
||||
{provide: PLATFORM_INITIALIZER, useValue: log.fn('platform_init1'), multi: true},
|
||||
{provide: PLATFORM_INITIALIZER, useValue: log.fn('platform_init2'), multi: true}
|
||||
])();
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
providers: [
|
||||
{provide: APP_INITIALIZER, useValue: log.fn('app_init1'), multi: true},
|
||||
{provide: APP_INITIALIZER, useValue: log.fn('app_init2'), multi: true}
|
||||
]
|
||||
})
|
||||
class SomeModule {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
expect(log.result()).toEqual('platform_init1; platform_init2');
|
||||
log.clear();
|
||||
p.bootstrapModule(SomeModule).then(() => {
|
||||
expect(log.result()).toEqual('app_init1; app_init2');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove styles when transitioning from a server render',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
@Component({
|
||||
selector: 'root',
|
||||
template: 'root',
|
||||
})
|
||||
class RootCmp {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [RootCmp],
|
||||
declarations: [RootCmp],
|
||||
imports: [BrowserModule.withServerTransition({appId: 'my-app'})],
|
||||
})
|
||||
class TestModule {
|
||||
}
|
||||
|
||||
// First, set up styles to be removed.
|
||||
const dom = getDOM();
|
||||
const platform = platformBrowserDynamic();
|
||||
const document = platform.injector.get(DOCUMENT);
|
||||
const style = dom.createElement('style', document);
|
||||
dom.setAttribute(style, 'ng-transition', 'my-app');
|
||||
dom.appendChild(document.head, style);
|
||||
|
||||
const root = dom.createElement('root', document);
|
||||
dom.appendChild(document.body, root);
|
||||
|
||||
platform.bootstrapModule(TestModule).then(() => {
|
||||
const styles: HTMLElement[] =
|
||||
Array.prototype.slice.apply(dom.getElementsByTagName(document, 'style') || []);
|
||||
styles.forEach(
|
||||
style => { expect(dom.getAttribute(style, 'ng-transition')).not.toBe('my-app'); });
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should register each application with the testability registry',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const refPromise1: Promise<ComponentRef<any>> = bootstrap(HelloRootCmp, testProviders);
|
||||
const refPromise2: Promise<ComponentRef<any>> = bootstrap(HelloRootCmp2, testProviders);
|
||||
|
||||
Promise.all([refPromise1, refPromise2]).then((refs: ComponentRef<any>[]) => {
|
||||
const registry = refs[0].injector.get(TestabilityRegistry);
|
||||
const testabilities =
|
||||
[refs[0].injector.get(Testability), refs[1].injector.get(Testability)];
|
||||
Promise.all(testabilities).then((testabilities: Testability[]) => {
|
||||
expect(registry.findTestabilityInTree(el)).toEqual(testabilities[0]);
|
||||
expect(registry.findTestabilityInTree(el2)).toEqual(testabilities[1]);
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should allow to pass schemas', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
bootstrap(HelloCmpUsingCustomElement, testProviders).then((compRef) => {
|
||||
expect(el).toHaveText('hello world!');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {parseCookieValue} from '../../src/browser/browser_adapter';
|
||||
|
||||
export function main() {
|
||||
describe('cookies', () => {
|
||||
it('parses cookies', () => {
|
||||
const cookie = 'other-cookie=false; xsrf-token=token-value; is_awesome=true; ffo=true;';
|
||||
expect(parseCookieValue(cookie, 'xsrf-token')).toBe('token-value');
|
||||
});
|
||||
it('handles encoded keys', () => {
|
||||
expect(parseCookieValue('whitespace%20token=token-value', 'whitespace token'))
|
||||
.toBe('token-value');
|
||||
});
|
||||
it('handles encoded values', () => {
|
||||
expect(parseCookieValue('token=whitespace%20', 'token')).toBe('whitespace ');
|
||||
expect(parseCookieValue('token=whitespace%0A', 'token')).toBe('whitespace\n');
|
||||
});
|
||||
it('sets cookie values', () => {
|
||||
getDOM().setCookie('my test cookie', 'my test value');
|
||||
getDOM().setCookie('my other cookie', 'my test value 2');
|
||||
expect(getDOM().getCookie('my test cookie')).toBe('my test value');
|
||||
});
|
||||
});
|
||||
}
|
200
packages/platform-browser/test/browser/meta_spec.ts
Normal file
200
packages/platform-browser/test/browser/meta_spec.ts
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BrowserModule, Meta} from '@angular/platform-browser';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('Meta service', () => {
|
||||
const doc = getDOM().createHtmlDocument();
|
||||
const metaService = new Meta(doc);
|
||||
let defaultMeta: HTMLMetaElement;
|
||||
|
||||
beforeEach(() => {
|
||||
defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement;
|
||||
getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id');
|
||||
getDOM().setAttribute(defaultMeta, 'content', '123456789');
|
||||
getDOM().appendChild(getDOM().getElementsByTagName(doc, 'head')[0], defaultMeta);
|
||||
});
|
||||
|
||||
afterEach(() => getDOM().remove(defaultMeta));
|
||||
|
||||
it('should return meta tag matching selector', () => {
|
||||
const actual: HTMLMetaElement = metaService.getTag('property="fb:app_id"');
|
||||
expect(actual).not.toBeNull();
|
||||
expect(getDOM().getAttribute(actual, 'content')).toEqual('123456789');
|
||||
});
|
||||
|
||||
it('should return all meta tags matching selector', () => {
|
||||
const tag1 = metaService.addTag({name: 'author', content: 'page author'});
|
||||
const tag2 = metaService.addTag({name: 'author', content: 'another page author'});
|
||||
|
||||
const actual: HTMLMetaElement[] = metaService.getTags('name=author');
|
||||
expect(actual.length).toEqual(2);
|
||||
expect(getDOM().getAttribute(actual[0], 'content')).toEqual('page author');
|
||||
expect(getDOM().getAttribute(actual[1], 'content')).toEqual('another page author');
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(tag1);
|
||||
metaService.removeTagElement(tag2);
|
||||
});
|
||||
|
||||
it('should return null if meta tag does not exist', () => {
|
||||
const actual: HTMLMetaElement = metaService.getTag('fake=fake');
|
||||
expect(actual).toBeNull();
|
||||
});
|
||||
|
||||
it('should remove meta tag by the given selector', () => {
|
||||
const selector = 'name=author';
|
||||
expect(metaService.getTag(selector)).toBeNull();
|
||||
|
||||
metaService.addTag({name: 'author', content: 'page author'});
|
||||
|
||||
expect(metaService.getTag(selector)).not.toBeNull();
|
||||
|
||||
metaService.removeTag(selector);
|
||||
|
||||
expect(metaService.getTag(selector)).toBeNull();
|
||||
});
|
||||
|
||||
it('should remove meta tag by the given element', () => {
|
||||
const selector = 'name=keywords';
|
||||
expect(metaService.getTag(selector)).toBeNull();
|
||||
|
||||
metaService.addTags([{name: 'keywords', content: 'meta test'}]);
|
||||
|
||||
const meta = metaService.getTag(selector);
|
||||
expect(meta).not.toBeNull();
|
||||
|
||||
metaService.removeTagElement(meta);
|
||||
|
||||
expect(metaService.getTag(selector)).toBeNull();
|
||||
});
|
||||
|
||||
it('should update meta tag matching the given selector', () => {
|
||||
const selector = 'property="fb:app_id"';
|
||||
metaService.updateTag({content: '4321'}, selector);
|
||||
|
||||
const actual = metaService.getTag(selector);
|
||||
expect(actual).not.toBeNull();
|
||||
expect(getDOM().getAttribute(actual, 'content')).toEqual('4321');
|
||||
});
|
||||
|
||||
it('should extract selector from the tag definition', () => {
|
||||
const selector = 'property="fb:app_id"';
|
||||
metaService.updateTag({property: 'fb:app_id', content: '666'});
|
||||
|
||||
const actual = metaService.getTag(selector);
|
||||
expect(actual).not.toBeNull();
|
||||
expect(getDOM().getAttribute(actual, 'content')).toEqual('666');
|
||||
});
|
||||
|
||||
it('should create meta tag if it does not exist', () => {
|
||||
const selector = 'name="twitter:title"';
|
||||
|
||||
metaService.updateTag({name: 'twitter:title', content: 'Content Title'}, selector);
|
||||
|
||||
const actual = metaService.getTag(selector);
|
||||
expect(actual).not.toBeNull();
|
||||
expect(getDOM().getAttribute(actual, 'content')).toEqual('Content Title');
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(actual);
|
||||
});
|
||||
|
||||
it('should add new meta tag', () => {
|
||||
const selector = 'name="og:title"';
|
||||
expect(metaService.getTag(selector)).toBeNull();
|
||||
|
||||
metaService.addTag({name: 'og:title', content: 'Content Title'});
|
||||
|
||||
const actual = metaService.getTag(selector);
|
||||
expect(actual).not.toBeNull();
|
||||
expect(getDOM().getAttribute(actual, 'content')).toEqual('Content Title');
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(actual);
|
||||
});
|
||||
|
||||
it('should add multiple new meta tags', () => {
|
||||
const nameSelector = 'name="twitter:title"';
|
||||
const propertySelector = 'property="og:title"';
|
||||
expect(metaService.getTag(nameSelector)).toBeNull();
|
||||
expect(metaService.getTag(propertySelector)).toBeNull();
|
||||
|
||||
metaService.addTags([
|
||||
{name: 'twitter:title', content: 'Content Title'},
|
||||
{property: 'og:title', content: 'Content Title'}
|
||||
]);
|
||||
const twitterMeta = metaService.getTag(nameSelector);
|
||||
const fbMeta = metaService.getTag(propertySelector);
|
||||
expect(twitterMeta).not.toBeNull();
|
||||
expect(fbMeta).not.toBeNull();
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(twitterMeta);
|
||||
metaService.removeTagElement(fbMeta);
|
||||
});
|
||||
|
||||
it('should not add meta tag if it is already present on the page and has the same attr', () => {
|
||||
const selector = 'property="fb:app_id"';
|
||||
expect(metaService.getTags(selector).length).toEqual(1);
|
||||
|
||||
metaService.addTag({property: 'fb:app_id', content: '123456789'});
|
||||
|
||||
expect(metaService.getTags(selector).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should add meta tag if it is already present on the page and but has different attr',
|
||||
() => {
|
||||
const selector = 'property="fb:app_id"';
|
||||
expect(metaService.getTags(selector).length).toEqual(1);
|
||||
|
||||
const meta = metaService.addTag({property: 'fb:app_id', content: '666'});
|
||||
|
||||
expect(metaService.getTags(selector).length).toEqual(2);
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(meta);
|
||||
});
|
||||
|
||||
it('should add meta tag if it is already present on the page and force true', () => {
|
||||
const selector = 'property="fb:app_id"';
|
||||
expect(metaService.getTags(selector).length).toEqual(1);
|
||||
|
||||
const meta = metaService.addTag({property: 'fb:app_id', content: '123456789'}, true);
|
||||
|
||||
expect(metaService.getTags(selector).length).toEqual(2);
|
||||
|
||||
// clean up
|
||||
metaService.removeTagElement(meta);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('integration test', () => {
|
||||
|
||||
@Injectable()
|
||||
class DependsOnMeta {
|
||||
constructor(public meta: Meta) {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [BrowserModule],
|
||||
providers: [DependsOnMeta],
|
||||
});
|
||||
});
|
||||
|
||||
it('should inject Meta service when using BrowserModule',
|
||||
() => expect(TestBed.get(DependsOnMeta).meta).toBeAnInstanceOf(Meta));
|
||||
});
|
||||
}
|
13
packages/platform-browser/test/browser/rectangle_mock.ts
Normal file
13
packages/platform-browser/test/browser/rectangle_mock.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export function createRectangle(
|
||||
left: any /** TODO #9100 */, top: any /** TODO #9100 */, width: any /** TODO #9100 */,
|
||||
height: any /** TODO #9100 */) {
|
||||
return {left, top, width, height};
|
||||
}
|
@ -0,0 +1 @@
|
||||
<p>hey</p>
|
55
packages/platform-browser/test/browser/title_spec.ts
Normal file
55
packages/platform-browser/test/browser/title_spec.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BrowserModule, Title} from '@angular/platform-browser';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('title service', () => {
|
||||
const doc = getDOM().createHtmlDocument();
|
||||
const initialTitle = getDOM().getTitle(doc);
|
||||
const titleService = new Title(doc);
|
||||
|
||||
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
|
||||
|
||||
it('should allow reading initial title',
|
||||
() => { expect(titleService.getTitle()).toEqual(initialTitle); });
|
||||
|
||||
it('should set a title on the injected document', () => {
|
||||
titleService.setTitle('test title');
|
||||
expect(getDOM().getTitle(doc)).toEqual('test title');
|
||||
expect(titleService.getTitle()).toEqual('test title');
|
||||
});
|
||||
|
||||
it('should reset title to empty string if title not provided', () => {
|
||||
titleService.setTitle(null);
|
||||
expect(getDOM().getTitle(doc)).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('integration test', () => {
|
||||
|
||||
@Injectable()
|
||||
class DependsOnTitle {
|
||||
constructor(public title: Title) {}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [BrowserModule],
|
||||
providers: [DependsOnTitle],
|
||||
});
|
||||
});
|
||||
|
||||
it('should inject Title service when using BrowserModule',
|
||||
() => { expect(TestBed.get(DependsOnTitle).title).toBeAnInstanceOf(Title); });
|
||||
});
|
||||
}
|
28
packages/platform-browser/test/browser/tools/spies.ts
Normal file
28
packages/platform-browser/test/browser/tools/spies.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ReflectiveInjector, ɵglobal as global} from '@angular/core';
|
||||
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
|
||||
import {SpyObject} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export class SpyApplicationRef extends SpyObject {
|
||||
constructor() { super(ApplicationRef_); }
|
||||
}
|
||||
|
||||
export class SpyComponentRef extends SpyObject {
|
||||
injector: any /** TODO #9100 */;
|
||||
constructor() {
|
||||
super();
|
||||
this.injector = ReflectiveInjector.resolveAndCreate(
|
||||
[{provide: ApplicationRef, useClass: SpyApplicationRef}]);
|
||||
}
|
||||
}
|
||||
|
||||
export function callNgProfilerTimeChangeDetection(config?: any /** TODO #9100 */): void {
|
||||
(<any>global).ng.profiler.timeChangeDetection(config);
|
||||
}
|
24
packages/platform-browser/test/browser/tools/tools_spec.ts
Normal file
24
packages/platform-browser/test/browser/tools/tools_spec.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {disableDebugTools, enableDebugTools} from '@angular/platform-browser';
|
||||
|
||||
import {SpyComponentRef, callNgProfilerTimeChangeDetection} from './spies';
|
||||
|
||||
export function main() {
|
||||
describe('profiler', () => {
|
||||
beforeEach(() => { enableDebugTools((<any>new SpyComponentRef())); });
|
||||
|
||||
afterEach(() => { disableDebugTools(); });
|
||||
|
||||
it('should time change detection', () => { callNgProfilerTimeChangeDetection(); });
|
||||
|
||||
it('should time change detection with recording',
|
||||
() => { callNgProfilerTimeChangeDetection({'record': true}); });
|
||||
});
|
||||
}
|
241
packages/platform-browser/test/browser_util_spec.ts
Normal file
241
packages/platform-browser/test/browser_util_spec.ts
Normal file
@ -0,0 +1,241 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import {BrowserDetection} from '../testing/browser_util';
|
||||
|
||||
export function main() {
|
||||
describe('BrowserDetection', () => {
|
||||
|
||||
const browsers = [
|
||||
{
|
||||
name: 'Chrome',
|
||||
ua: 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: true,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Chrome mobile',
|
||||
ua: 'Mozilla/5.0 (Linux; Android 5.1.1; D5803 Build/23.4.A.0.546) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.133 Mobile Safari/537.36',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Firefox',
|
||||
ua: 'Mozilla/5.0 (X11; Linux i686; rv:40.0) Gecko/20100101 Firefox/40.0',
|
||||
isFirefox: true,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: false,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'IE9',
|
||||
ua: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727)',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: true,
|
||||
isWebkit: false,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'IE10',
|
||||
ua: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C)',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: true,
|
||||
isWebkit: false,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'IE11',
|
||||
ua: 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; .NET4.0E; .NET4.0C; rv:11.0) like Gecko',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: true,
|
||||
isWebkit: false,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'IEMobile',
|
||||
ua: 'Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 520) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: true,
|
||||
isWebkit: false,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Edge',
|
||||
ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10136',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: true,
|
||||
isIE: false,
|
||||
isWebkit: false,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Android4.1',
|
||||
ua: 'Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Android SDK built for x86 Build/JRO03H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
isFirefox: false,
|
||||
isAndroid: true,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Android4.2',
|
||||
ua: 'Mozilla/5.0 (Linux; U; Android 4.2; en-us; Android SDK built for x86 Build/JOP40C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
isFirefox: false,
|
||||
isAndroid: true,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Android4.3',
|
||||
ua: 'Mozilla/5.0 (Linux; U; Android 4.3; en-us; Android SDK built for x86 Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
|
||||
isFirefox: false,
|
||||
isAndroid: true,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Android4.4',
|
||||
ua: 'Mozilla/5.0 (Linux; Android 4.4.2; Android SDK built for x86 Build/KK) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: true
|
||||
},
|
||||
{
|
||||
name: 'Safari7',
|
||||
ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/7.1.7 Safari/537.85.16',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'Safari8',
|
||||
ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'iOS7',
|
||||
ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 7_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D167 Safari/9537.53',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: true,
|
||||
isSlow: true,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
},
|
||||
{
|
||||
name: 'iOS8',
|
||||
ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H141 Safari/600.1.4',
|
||||
isFirefox: false,
|
||||
isAndroid: false,
|
||||
isEdge: false,
|
||||
isIE: false,
|
||||
isWebkit: true,
|
||||
isIOS7: false,
|
||||
isSlow: false,
|
||||
isChromeDesktop: false,
|
||||
isOldChrome: false
|
||||
}
|
||||
];
|
||||
|
||||
browsers.forEach((browser: {[key: string]: any}) => {
|
||||
it(`should detect ${browser[ 'name']}`, () => {
|
||||
const bd = new BrowserDetection(<string>browser['ua']);
|
||||
expect(bd.isFirefox).toBe(browser['isFirefox']);
|
||||
expect(bd.isAndroid).toBe(browser['isAndroid']);
|
||||
expect(bd.isEdge).toBe(browser['isEdge']);
|
||||
expect(bd.isIE).toBe(browser['isIE']);
|
||||
expect(bd.isWebkit).toBe(browser['isWebkit']);
|
||||
expect(bd.isIOS7).toBe(browser['isIOS7']);
|
||||
expect(bd.isSlow).toBe(browser['isSlow']);
|
||||
expect(bd.isChromeDesktop).toBe(browser['isChromeDesktop']);
|
||||
expect(bd.isOldChrome).toBe(browser['isOldChrome']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
98
packages/platform-browser/test/dom/dom_renderer_spec.ts
Normal file
98
packages/platform-browser/test/dom/dom_renderer_spec.ts
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('DomRenderer', () => {
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({imports: [BrowserModule, TestModule]}));
|
||||
|
||||
// other browsers don't support shadow dom
|
||||
if (browserDetection.isChromeDesktop) {
|
||||
it('should allow to style components with emulated encapsulation and no encapsulation inside of components with shadow DOM',
|
||||
() => {
|
||||
TestBed.overrideComponent(CmpEncapsulationNative, {
|
||||
set: {
|
||||
template:
|
||||
'<div class="native"></div><cmp-emulated></cmp-emulated><cmp-none></cmp-none>'
|
||||
}
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(SomeApp);
|
||||
|
||||
const cmp = fixture.debugElement.query(By.css('cmp-native')).nativeElement;
|
||||
|
||||
|
||||
const native = cmp.shadowRoot.querySelector('.native');
|
||||
expect(window.getComputedStyle(native).color).toEqual('rgb(255, 0, 0)');
|
||||
|
||||
const emulated = cmp.shadowRoot.querySelector('.emulated');
|
||||
expect(window.getComputedStyle(emulated).color).toEqual('rgb(0, 0, 255)');
|
||||
|
||||
const none = cmp.shadowRoot.querySelector('.none');
|
||||
expect(window.getComputedStyle(none).color).toEqual('rgb(0, 255, 0)');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cmp-native',
|
||||
template: `<div class="native"></div>`,
|
||||
styles: [`.native { color: red; }`],
|
||||
encapsulation: ViewEncapsulation.Native
|
||||
})
|
||||
class CmpEncapsulationNative {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cmp-emulated',
|
||||
template: `<div class="emulated"></div>`,
|
||||
styles: [`.emulated { color: blue; }`],
|
||||
encapsulation: ViewEncapsulation.Emulated
|
||||
})
|
||||
class CmpEncapsulationEmulated {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'cmp-none',
|
||||
template: `<div class="none"></div>`,
|
||||
styles: [`.none { color: lime; }`],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
class CmpEncapsulationNone {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'some-app',
|
||||
template: `
|
||||
<cmp-native></cmp-native>
|
||||
<cmp-emulated></cmp-emulated>
|
||||
<cmp-none></cmp-none>
|
||||
`,
|
||||
})
|
||||
export class SomeApp {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SomeApp,
|
||||
CmpEncapsulationNative,
|
||||
CmpEncapsulationEmulated,
|
||||
CmpEncapsulationNone,
|
||||
],
|
||||
imports: [CommonModule]
|
||||
})
|
||||
class TestModule {
|
||||
}
|
113
packages/platform-browser/test/dom/events/event_manager_spec.ts
Normal file
113
packages/platform-browser/test/dom/events/event_manager_spec.ts
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgZone} from '@angular/core/src/zone/ng_zone';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DomEventsPlugin} from '@angular/platform-browser/src/dom/events/dom_events';
|
||||
import {EventManager, EventManagerPlugin} from '@angular/platform-browser/src/dom/events/event_manager';
|
||||
import {el} from '../../../testing/browser_util';
|
||||
|
||||
export function main() {
|
||||
let domEventPlugin: DomEventsPlugin;
|
||||
let doc: any;
|
||||
|
||||
describe('EventManager', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
domEventPlugin = new DomEventsPlugin(doc);
|
||||
});
|
||||
|
||||
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
|
||||
() => {
|
||||
const element = el('<div></div>');
|
||||
const handler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['click']);
|
||||
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
expect(plugin.eventHandler['click']).toBe(handler);
|
||||
});
|
||||
|
||||
it('should delegate event bindings to the first plugin supporting the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const clickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const dblClickHandler = (e: any /** TODO #9100 */) => e;
|
||||
const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
|
||||
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', clickHandler);
|
||||
manager.addEventListener(element, 'dblclick', dblClickHandler);
|
||||
expect(plugin2.eventHandler['click']).toBe(clickHandler);
|
||||
expect(plugin1.eventHandler['dblclick']).toBe(dblClickHandler);
|
||||
});
|
||||
|
||||
it('should throw when no plugin can handle the event', () => {
|
||||
const element = el('<div></div>');
|
||||
const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
|
||||
const manager = new EventManager([plugin], new FakeNgZone());
|
||||
expect(() => manager.addEventListener(element, 'click', null))
|
||||
.toThrowError('No event manager plugin found for event click');
|
||||
});
|
||||
|
||||
it('events are caught when fired from a child', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
// Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
|
||||
getDOM().appendChild(doc.body, element);
|
||||
|
||||
const child = getDOM().firstChild(element);
|
||||
const dispatchedEvent = getDOM().createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
manager.addEventListener(element, 'click', handler);
|
||||
getDOM().dispatchEvent(child, dispatchedEvent);
|
||||
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
});
|
||||
|
||||
it('should add and remove global event listeners', () => {
|
||||
const element = el('<div><div></div></div>');
|
||||
getDOM().appendChild(doc.body, element);
|
||||
const dispatchedEvent = getDOM().createMouseEvent('click');
|
||||
let receivedEvent: any /** TODO #9100 */ = null;
|
||||
const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
|
||||
const manager = new EventManager([domEventPlugin], new FakeNgZone());
|
||||
|
||||
const remover = manager.addGlobalEventListener('document', 'click', handler);
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(dispatchedEvent);
|
||||
|
||||
receivedEvent = null;
|
||||
remover();
|
||||
getDOM().dispatchEvent(element, dispatchedEvent);
|
||||
expect(receivedEvent).toBe(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
class FakeEventManagerPlugin extends EventManagerPlugin {
|
||||
eventHandler: {[event: string]: Function} = {};
|
||||
|
||||
constructor(doc: any, public supportedEvents: string[]) { super(doc); }
|
||||
|
||||
supports(eventName: string): boolean { return this.supportedEvents.indexOf(eventName) > -1; }
|
||||
|
||||
addEventListener(element: any, eventName: string, handler: Function) {
|
||||
this.eventHandler[eventName] = handler;
|
||||
return () => { delete (this.eventHandler[eventName]); };
|
||||
}
|
||||
}
|
||||
|
||||
class FakeNgZone extends NgZone {
|
||||
constructor() { super({enableLongStackTrace: false}); }
|
||||
run(fn: Function) { fn(); }
|
||||
|
||||
runOutsideAngular(fn: Function) { return fn(); }
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {HammerGestureConfig, HammerGesturesPlugin} from '@angular/platform-browser/src/dom/events/hammer_gestures';
|
||||
|
||||
export function main() {
|
||||
describe('HammerGesturesPlugin', () => {
|
||||
|
||||
it('should implement addGlobalEventListener', () => {
|
||||
const plugin = new HammerGesturesPlugin(document, new HammerGestureConfig());
|
||||
|
||||
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
||||
|
||||
expect(() => plugin.addGlobalEventListener('document', 'swipe', () => {})).not.toThrowError();
|
||||
});
|
||||
});
|
||||
}
|
71
packages/platform-browser/test/dom/events/key_events_spec.ts
Normal file
71
packages/platform-browser/test/dom/events/key_events_spec.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {KeyEventsPlugin} from '@angular/platform-browser/src/dom/events/key_events';
|
||||
|
||||
export function main() {
|
||||
describe('KeyEventsPlugin', () => {
|
||||
|
||||
it('should ignore unrecognized events', () => {
|
||||
expect(KeyEventsPlugin.parseEventName('keydown')).toEqual(null);
|
||||
expect(KeyEventsPlugin.parseEventName('keyup')).toEqual(null);
|
||||
expect(KeyEventsPlugin.parseEventName('keydown.unknownmodifier.enter')).toEqual(null);
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.unknownmodifier.enter')).toEqual(null);
|
||||
expect(KeyEventsPlugin.parseEventName('unknownevent.control.shift.enter')).toEqual(null);
|
||||
expect(KeyEventsPlugin.parseEventName('unknownevent.enter')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should correctly parse event names', () => {
|
||||
// key with no modifier
|
||||
expect(KeyEventsPlugin.parseEventName('keydown.enter'))
|
||||
.toEqual({'domEventName': 'keydown', 'fullKey': 'enter'});
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.enter'))
|
||||
.toEqual({'domEventName': 'keyup', 'fullKey': 'enter'});
|
||||
|
||||
// key with modifiers:
|
||||
expect(KeyEventsPlugin.parseEventName('keydown.control.shift.enter'))
|
||||
.toEqual({'domEventName': 'keydown', 'fullKey': 'control.shift.enter'});
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.control.shift.enter'))
|
||||
.toEqual({'domEventName': 'keyup', 'fullKey': 'control.shift.enter'});
|
||||
|
||||
// key with modifiers in a different order:
|
||||
expect(KeyEventsPlugin.parseEventName('keydown.shift.control.enter'))
|
||||
.toEqual({'domEventName': 'keydown', 'fullKey': 'control.shift.enter'});
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.shift.control.enter'))
|
||||
.toEqual({'domEventName': 'keyup', 'fullKey': 'control.shift.enter'});
|
||||
|
||||
// key that is also a modifier:
|
||||
expect(KeyEventsPlugin.parseEventName('keydown.shift.control'))
|
||||
.toEqual({'domEventName': 'keydown', 'fullKey': 'shift.control'});
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.shift.control'))
|
||||
.toEqual({'domEventName': 'keyup', 'fullKey': 'shift.control'});
|
||||
|
||||
expect(KeyEventsPlugin.parseEventName('keydown.control.shift'))
|
||||
.toEqual({'domEventName': 'keydown', 'fullKey': 'control.shift'});
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.control.shift'))
|
||||
.toEqual({'domEventName': 'keyup', 'fullKey': 'control.shift'});
|
||||
|
||||
});
|
||||
|
||||
it('should alias esc to escape', () => {
|
||||
expect(KeyEventsPlugin.parseEventName('keyup.control.esc'))
|
||||
.toEqual(KeyEventsPlugin.parseEventName('keyup.control.escape'));
|
||||
});
|
||||
|
||||
it('should implement addGlobalEventListener', () => {
|
||||
const plugin = new KeyEventsPlugin(document);
|
||||
|
||||
spyOn(plugin, 'addEventListener').and.callFake(() => {});
|
||||
|
||||
expect(() => plugin.addGlobalEventListener('window', 'keyup.control.esc', () => {}))
|
||||
.not.toThrowError();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {beforeEach, describe, it} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DomSharedStylesHost} from '@angular/platform-browser/src/dom/shared_styles_host';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('DomSharedStylesHost', () => {
|
||||
let doc: Document;
|
||||
let ssh: DomSharedStylesHost;
|
||||
let someHost: Element;
|
||||
beforeEach(() => {
|
||||
doc = getDOM().createHtmlDocument();
|
||||
doc.title = '';
|
||||
ssh = new DomSharedStylesHost(doc);
|
||||
someHost = getDOM().createElement('div');
|
||||
});
|
||||
|
||||
it('should add existing styles to new hosts', () => {
|
||||
ssh.addStyles(['a {};']);
|
||||
ssh.addHost(someHost);
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('<style>a {};</style>');
|
||||
});
|
||||
|
||||
it('should add new styles to hosts', () => {
|
||||
ssh.addHost(someHost);
|
||||
ssh.addStyles(['a {};']);
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('<style>a {};</style>');
|
||||
});
|
||||
|
||||
it('should add styles only once to hosts', () => {
|
||||
ssh.addStyles(['a {};']);
|
||||
ssh.addHost(someHost);
|
||||
ssh.addStyles(['a {};']);
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('<style>a {};</style>');
|
||||
});
|
||||
|
||||
it('should use the document head as default host', () => {
|
||||
ssh.addStyles(['a {};', 'b {};']);
|
||||
expect(doc.head).toHaveText('a {};b {};');
|
||||
});
|
||||
|
||||
it('should remove style nodes on destroy', () => {
|
||||
ssh.addStyles(['a {};']);
|
||||
ssh.addHost(someHost);
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('<style>a {};</style>');
|
||||
|
||||
ssh.ngOnDestroy();
|
||||
expect(getDOM().getInnerHTML(someHost)).toEqual('');
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SecurityContext} from '@angular/core';
|
||||
import * as t from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {DomSanitizerImpl} from '../../src/security/dom_sanitization_service';
|
||||
|
||||
export function main() {
|
||||
t.describe('DOM Sanitization Service', () => {
|
||||
t.it('accepts resource URL values for resource contexts', () => {
|
||||
const svc = new DomSanitizerImpl(null);
|
||||
const resourceUrl = svc.bypassSecurityTrustResourceUrl('http://hello/world');
|
||||
t.expect(svc.sanitize(SecurityContext.URL, resourceUrl)).toBe('http://hello/world');
|
||||
});
|
||||
});
|
||||
}
|
113
packages/platform-browser/test/security/html_sanitizer_spec.ts
Normal file
113
packages/platform-browser/test/security/html_sanitizer_spec.ts
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as t from '@angular/core/testing/testing_internal';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
|
||||
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {sanitizeHtml} from '../../src/security/html_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('HTML sanitizer', () => {
|
||||
let defaultDoc: any;
|
||||
let originalLog: (msg: any) => any = null;
|
||||
let logMsgs: string[];
|
||||
|
||||
t.beforeEach(() => {
|
||||
defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
});
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
t.it('serializes nested structures', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>'))
|
||||
.toEqual('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('serializes self closing elements', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello <br> World</p>'))
|
||||
.toEqual('<p>Hello <br> World</p>');
|
||||
});
|
||||
t.it('supports namespaced elements', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, 'a<my:hr/><my:div>b</my:div>c')).toEqual('abc');
|
||||
});
|
||||
t.it('supports namespaced attributes', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="something">t</a>'))
|
||||
.toEqual('<a xlink:href="something">t</a>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:evil="something">t</a>')).toEqual('<a>t</a>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="javascript:foo()">t</a>'))
|
||||
.toEqual('<a xlink:href="unsafe:javascript:foo()">t</a>');
|
||||
});
|
||||
t.it('supports HTML5 elements', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<main><summary>Works</summary></main>'))
|
||||
.toEqual('<main><summary>Works</summary></main>');
|
||||
});
|
||||
t.it('sanitizes srcset attributes', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<img srcset="/foo.png 400px, javascript:evil() 23px">'))
|
||||
.toEqual('<img srcset="/foo.png 400px, unsafe:javascript:evil() 23px">');
|
||||
});
|
||||
|
||||
t.it('supports sanitizing plain text', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World');
|
||||
});
|
||||
t.it('ignores non-element, non-attribute nodes', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<!-- comments? -->no.')).toEqual('no.');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<?pi nodes?>no.')).toEqual('no.');
|
||||
t.expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/);
|
||||
});
|
||||
t.it('supports sanitizing escaped entities', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '🚀')).toEqual('🚀');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('does not warn when just re-encoding text', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hellö Wörld</p>'))
|
||||
.toEqual('<p>Hellö Wörld</p>');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('escapes entities', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>'))
|
||||
.toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p alt="% & " !">Hello</p>'))
|
||||
.toEqual('<p alt="% & " !">Hello</p>'); // NB: quote encoded as ASCII ".
|
||||
});
|
||||
t.describe('should strip dangerous elements', () => {
|
||||
const dangerousTags = [
|
||||
'frameset', 'form', 'param', 'object', 'embed', 'textarea', 'input', 'button', 'option',
|
||||
'select', 'script', 'style', 'link', 'base', 'basefont'
|
||||
];
|
||||
|
||||
for (const tag of dangerousTags) {
|
||||
t.it(`${tag}`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<${tag}>evil!</${tag}>`)).toEqual('evil!');
|
||||
});
|
||||
}
|
||||
t.it(`swallows frame entirely`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<frame>evil!</frame>`)).not.toContain('<frame>');
|
||||
});
|
||||
});
|
||||
t.describe('should strip dangerous attributes', () => {
|
||||
const dangerousAttrs = ['id', 'name', 'style'];
|
||||
|
||||
for (const attr of dangerousAttrs) {
|
||||
t.it(`${attr}`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<a ${attr}="x">evil!</a>`)).toEqual('<a>evil!</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (browserDetection.isWebkit) {
|
||||
t.it('should prevent mXSS attacks', function() {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a href=" javascript:alert(1)">CLICKME</a>'))
|
||||
.toEqual('<a href="unsafe:javascript:alert(1)">CLICKME</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as t from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {sanitizeStyle} from '../../src/security/style_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('Style sanitizer', () => {
|
||||
let logMsgs: string[];
|
||||
let originalLog: (msg: any) => any;
|
||||
|
||||
t.beforeEach(() => {
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
});
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
function expectSanitize(v: string) { return t.expect(sanitizeStyle(v)); }
|
||||
|
||||
t.it('sanitizes values', () => {
|
||||
expectSanitize('').toEqual('');
|
||||
expectSanitize('abc').toEqual('abc');
|
||||
expectSanitize('50px').toEqual('50px');
|
||||
expectSanitize('rgb(255, 0, 0)').toEqual('rgb(255, 0, 0)');
|
||||
expectSanitize('expression(haha)').toEqual('unsafe');
|
||||
});
|
||||
t.it('rejects unblanaced quotes', () => { expectSanitize('"value" "').toEqual('unsafe'); });
|
||||
t.it('accepts transform functions', () => {
|
||||
expectSanitize('rotate(90deg)').toEqual('rotate(90deg)');
|
||||
expectSanitize('rotate(javascript:evil())').toEqual('unsafe');
|
||||
expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)');
|
||||
expectSanitize('scale3d(1, 1, 2)').toEqual('scale3d(1, 1, 2)');
|
||||
});
|
||||
t.it('accepts gradients', () => {
|
||||
expectSanitize('linear-gradient(to bottom, #fg34a1, #bada55)')
|
||||
.toEqual('linear-gradient(to bottom, #fg34a1, #bada55)');
|
||||
expectSanitize('repeating-radial-gradient(ellipse cover, black, red, black, red)')
|
||||
.toEqual('repeating-radial-gradient(ellipse cover, black, red, black, red)');
|
||||
});
|
||||
t.it('accepts calc', () => { expectSanitize('calc(90%-123px)').toEqual('calc(90%-123px)'); });
|
||||
t.it('accepts attr', () => {
|
||||
expectSanitize('attr(value string)').toEqual('attr(value string)');
|
||||
});
|
||||
t.it('sanitizes URLs', () => {
|
||||
expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)');
|
||||
expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )');
|
||||
expectSanitize('url(javascript:evil())').toEqual('unsafe');
|
||||
expectSanitize('url(strangeprotocol:evil)').toEqual('unsafe');
|
||||
});
|
||||
t.it('accepts quoted URLs', () => {
|
||||
expectSanitize('url("foo/bar.png")').toEqual('url("foo/bar.png")');
|
||||
expectSanitize(`url('foo/bar.png')`).toEqual(`url('foo/bar.png')`);
|
||||
expectSanitize(`url( 'foo/bar.png'\n )`).toEqual(`url( 'foo/bar.png'\n )`);
|
||||
expectSanitize('url("javascript:evil()")').toEqual('unsafe');
|
||||
expectSanitize('url( " javascript:evil() " )').toEqual('unsafe');
|
||||
});
|
||||
});
|
||||
}
|
118
packages/platform-browser/test/security/url_sanitizer_spec.ts
Normal file
118
packages/platform-browser/test/security/url_sanitizer_spec.ts
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as t from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {sanitizeSrcset, sanitizeUrl} from '../../src/security/url_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('URL sanitizer', () => {
|
||||
let logMsgs: string[];
|
||||
let originalLog: (msg: any) => any;
|
||||
|
||||
t.beforeEach(() => {
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
});
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
t.it('reports unsafe URLs', () => {
|
||||
t.expect(sanitizeUrl('javascript:evil()')).toBe('unsafe:javascript:evil()');
|
||||
t.expect(logMsgs.join('\n')).toMatch(/sanitizing unsafe URL value/);
|
||||
});
|
||||
|
||||
t.describe('valid URLs', () => {
|
||||
const validUrls = [
|
||||
'',
|
||||
'http://abc',
|
||||
'HTTP://abc',
|
||||
'https://abc',
|
||||
'HTTPS://abc',
|
||||
'ftp://abc',
|
||||
'FTP://abc',
|
||||
'mailto:me@example.com',
|
||||
'MAILTO:me@example.com',
|
||||
'tel:123-123-1234',
|
||||
'TEL:123-123-1234',
|
||||
'#anchor',
|
||||
'/page1.md',
|
||||
'http://JavaScript/my.js',
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
|
||||
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
];
|
||||
for (const url of validUrls) {
|
||||
t.it(`valid ${url}`, () => t.expect(sanitizeUrl(url)).toEqual(url));
|
||||
}
|
||||
});
|
||||
|
||||
t.describe('invalid URLs', () => {
|
||||
const invalidUrls = [
|
||||
'javascript:evil()',
|
||||
'JavaScript:abc',
|
||||
'evilNewProtocol:abc',
|
||||
' \n Java\n Script:abc',
|
||||
'javascript:',
|
||||
'javascript:',
|
||||
'j avascript:',
|
||||
'javascript:',
|
||||
'javascript:',
|
||||
'jav	ascript:alert();',
|
||||
'jav\u0000ascript:alert();',
|
||||
'data:;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:text/javascript;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:application/x-msdownload;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
];
|
||||
for (const url of invalidUrls) {
|
||||
t.it(`valid ${url}`, () => t.expect(sanitizeUrl(url)).toMatch(/^unsafe:/));
|
||||
}
|
||||
});
|
||||
|
||||
t.describe('valid srcsets', () => {
|
||||
const validSrcsets = [
|
||||
'',
|
||||
'http://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png, http://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png, http://angular.io/images/test.png, http://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png 2x',
|
||||
'http://angular.io/images/test.png 2x, http://angular.io/images/test.png 3x',
|
||||
'http://angular.io/images/test.png 1.5x',
|
||||
'http://angular.io/images/test.png 1.25x',
|
||||
'http://angular.io/images/test.png 200w, http://angular.io/images/test.png 300w',
|
||||
'https://angular.io/images/test.png, http://angular.io/images/test.png',
|
||||
'http://angular.io:80/images/test.png, http://angular.io:8080/images/test.png',
|
||||
'http://www.angular.io:80/images/test.png, http://www.angular.io:8080/images/test.png',
|
||||
'https://angular.io/images/test.png, https://angular.io/images/test.png',
|
||||
'//angular.io/images/test.png, //angular.io/images/test.png',
|
||||
'/images/test.png, /images/test.png',
|
||||
'images/test.png, images/test.png',
|
||||
'http://angular.io/images/test.png?12345, http://angular.io/images/test.png?12345',
|
||||
'http://angular.io/images/test.png?maxage, http://angular.io/images/test.png?maxage',
|
||||
'http://angular.io/images/test.png?maxage=234, http://angular.io/images/test.png?maxage=234',
|
||||
];
|
||||
for (const srcset of validSrcsets) {
|
||||
t.it(`valid ${srcset}`, () => t.expect(sanitizeSrcset(srcset)).toEqual(srcset));
|
||||
}
|
||||
});
|
||||
|
||||
t.describe('invalid srcsets', () => {
|
||||
const invalidSrcsets = [
|
||||
'ht:tp://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png, ht:tp://angular.io/images/test.png',
|
||||
];
|
||||
for (const srcset of invalidSrcsets) {
|
||||
t.it(`valid ${srcset}`, () => t.expect(sanitizeSrcset(srcset)).toMatch(/unsafe:/));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
1
packages/platform-browser/test/static_assets/test.html
Normal file
1
packages/platform-browser/test/static_assets/test.html
Normal file
@ -0,0 +1 @@
|
||||
<span>from external template</span>
|
640
packages/platform-browser/test/testing_public_spec.ts
Normal file
640
packages/platform-browser/test/testing_public_spec.ts
Normal file
@ -0,0 +1,640 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompilerConfig, ResourceLoader} from '@angular/compiler';
|
||||
import {CUSTOM_ELEMENTS_SCHEMA, Component, Directive, Injectable, Input, NgModule, Pipe, ɵstringify as stringify} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, getTestBed, inject, tick, withModule} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
// Services, and components for the tests.
|
||||
|
||||
@Component({selector: 'child-comp', template: `<span>Original {{childBinding}}</span>`})
|
||||
@Injectable()
|
||||
class ChildComp {
|
||||
childBinding: string;
|
||||
constructor() { this.childBinding = 'Child'; }
|
||||
}
|
||||
|
||||
@Component({selector: 'child-comp', template: `<span>Mock</span>`})
|
||||
@Injectable()
|
||||
class MockChildComp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'parent-comp',
|
||||
template: `Parent(<child-comp></child-comp>)`,
|
||||
})
|
||||
@Injectable()
|
||||
class ParentComp {
|
||||
}
|
||||
|
||||
@Component({selector: 'my-if-comp', template: `MyIf(<span *ngIf="showMore">More</span>)`})
|
||||
@Injectable()
|
||||
class MyIfComp {
|
||||
showMore: boolean = false;
|
||||
}
|
||||
|
||||
@Component({selector: 'child-child-comp', template: `<span>ChildChild</span>`})
|
||||
@Injectable()
|
||||
class ChildChildComp {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-comp',
|
||||
template: `<span>Original {{childBinding}}(<child-child-comp></child-child-comp>)</span>`,
|
||||
})
|
||||
@Injectable()
|
||||
class ChildWithChildComp {
|
||||
childBinding: string;
|
||||
constructor() { this.childBinding = 'Child'; }
|
||||
}
|
||||
|
||||
class FancyService {
|
||||
value: string = 'real value';
|
||||
getAsyncValue() { return Promise.resolve('async value'); }
|
||||
getTimeoutValue() {
|
||||
return new Promise<string>((resolve, reject) => setTimeout(() => resolve('timeout value'), 10));
|
||||
}
|
||||
}
|
||||
|
||||
class MockFancyService extends FancyService {
|
||||
value: string = 'mocked out value';
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-service-comp',
|
||||
providers: [FancyService],
|
||||
template: `injected value: {{fancyService.value}}`
|
||||
})
|
||||
class TestProvidersComp {
|
||||
constructor(private fancyService: FancyService) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-service-comp',
|
||||
viewProviders: [FancyService],
|
||||
template: `injected value: {{fancyService.value}}`
|
||||
})
|
||||
class TestViewProvidersComp {
|
||||
constructor(private fancyService: FancyService) {}
|
||||
}
|
||||
|
||||
@Directive({selector: '[someDir]', host: {'[title]': 'someDir'}})
|
||||
class SomeDirective {
|
||||
@Input()
|
||||
someDir: string;
|
||||
}
|
||||
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
transform(value: string) { return `transformed ${value}`; }
|
||||
}
|
||||
|
||||
@Component({selector: 'comp', template: `<div [someDir]="'someValue' | somePipe"></div>`})
|
||||
class CompUsingModuleDirectiveAndPipe {
|
||||
}
|
||||
|
||||
@NgModule()
|
||||
class SomeLibModule {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
templateUrl: '/base/modules/@angular/platform-browser/test/static_assets/test.html'
|
||||
})
|
||||
class CompWithUrlTemplate {
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('public testing API', () => {
|
||||
describe('using the async helper with context passing', () => {
|
||||
beforeEach(function() { this.actuallyDone = false; });
|
||||
|
||||
afterEach(function() { expect(this.actuallyDone).toEqual(true); });
|
||||
|
||||
it('should run normal tests', function() { this.actuallyDone = true; });
|
||||
|
||||
it('should run normal async tests', function(done) {
|
||||
setTimeout(() => {
|
||||
this.actuallyDone = true;
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should run async tests with tasks',
|
||||
async(function() { setTimeout(() => this.actuallyDone = true, 0); }));
|
||||
|
||||
it('should run async tests with promises', async(function() {
|
||||
const p = new Promise((resolve, reject) => setTimeout(resolve, 10));
|
||||
p.then(() => this.actuallyDone = true);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('basic context passing to inject, fakeAsync and withModule helpers', () => {
|
||||
const moduleConfig = {
|
||||
providers: [FancyService],
|
||||
};
|
||||
|
||||
beforeEach(function() { this.contextModified = false; });
|
||||
|
||||
afterEach(function() { expect(this.contextModified).toEqual(true); });
|
||||
|
||||
it('should pass context to inject helper',
|
||||
inject([], function() { this.contextModified = true; }));
|
||||
|
||||
it('should pass context to fakeAsync helper',
|
||||
fakeAsync(function() { this.contextModified = true; }));
|
||||
|
||||
it('should pass context to withModule helper - simple',
|
||||
withModule(moduleConfig, function() { this.contextModified = true; }));
|
||||
|
||||
it('should pass context to withModule helper - advanced',
|
||||
withModule(moduleConfig).inject([FancyService], function(service: FancyService) {
|
||||
expect(service.value).toBe('real value');
|
||||
this.contextModified = true;
|
||||
}));
|
||||
|
||||
it('should preserve context when async and inject helpers are combined',
|
||||
async(inject([], function() { setTimeout(() => this.contextModified = true, 0); })));
|
||||
|
||||
it('should preserve context when fakeAsync and inject helpers are combined',
|
||||
fakeAsync(inject([], function() {
|
||||
setTimeout(() => this.contextModified = true, 0);
|
||||
tick(1);
|
||||
})));
|
||||
});
|
||||
|
||||
describe('using the test injector with the inject helper', () => {
|
||||
describe('setting up Providers', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{providers: [{provide: FancyService, useValue: new FancyService()}]});
|
||||
|
||||
it('should use set up providers', inject([FancyService], (service: FancyService) => {
|
||||
expect(service.value).toEqual('real value');
|
||||
}));
|
||||
|
||||
it('should wait until returned promises',
|
||||
async(inject([FancyService], (service: FancyService) => {
|
||||
service.getAsyncValue().then((value) => expect(value).toEqual('async value'));
|
||||
service.getTimeoutValue().then((value) => expect(value).toEqual('timeout value'));
|
||||
})));
|
||||
|
||||
it('should allow the use of fakeAsync',
|
||||
fakeAsync(inject([FancyService], (service: FancyService) => {
|
||||
let value: string;
|
||||
service.getAsyncValue().then((val) => value = val);
|
||||
tick();
|
||||
expect(value).toEqual('async value');
|
||||
})));
|
||||
|
||||
it('should allow use of "done"', (done) => {
|
||||
inject([FancyService], (service: FancyService) => {
|
||||
let count = 0;
|
||||
const id = setInterval(() => {
|
||||
count++;
|
||||
if (count > 2) {
|
||||
clearInterval(id);
|
||||
done();
|
||||
}
|
||||
}, 5);
|
||||
})(); // inject needs to be invoked explicitly with ().
|
||||
});
|
||||
|
||||
describe('using beforeEach', () => {
|
||||
beforeEach(inject([FancyService], (service: FancyService) => {
|
||||
service.value = 'value modified in beforeEach';
|
||||
}));
|
||||
|
||||
it('should use modified providers', inject([FancyService], (service: FancyService) => {
|
||||
expect(service.value).toEqual('value modified in beforeEach');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('using async beforeEach', () => {
|
||||
beforeEach(async(inject([FancyService], (service: FancyService) => {
|
||||
service.getAsyncValue().then((value) => service.value = value);
|
||||
})));
|
||||
|
||||
it('should use asynchronously modified value',
|
||||
inject([FancyService], (service: FancyService) => {
|
||||
expect(service.value).toEqual('async value');
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('using the test injector with modules', () => {
|
||||
const moduleConfig = {
|
||||
providers: [FancyService],
|
||||
imports: [SomeLibModule],
|
||||
declarations: [SomeDirective, SomePipe, CompUsingModuleDirectiveAndPipe],
|
||||
};
|
||||
|
||||
describe('setting up a module', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule(moduleConfig));
|
||||
|
||||
it('should use set up providers', inject([FancyService], (service: FancyService) => {
|
||||
expect(service.value).toEqual('real value');
|
||||
}));
|
||||
|
||||
it('should be able to create any declared components', () => {
|
||||
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
|
||||
expect(compFixture.componentInstance).toBeAnInstanceOf(CompUsingModuleDirectiveAndPipe);
|
||||
});
|
||||
|
||||
it('should use set up directives and pipes', () => {
|
||||
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
|
||||
const el = compFixture.debugElement;
|
||||
|
||||
compFixture.detectChanges();
|
||||
expect(el.children[0].properties['title']).toBe('transformed someValue');
|
||||
});
|
||||
|
||||
it('should use set up imported modules',
|
||||
inject([SomeLibModule], (libModule: SomeLibModule) => {
|
||||
expect(libModule).toBeAnInstanceOf(SomeLibModule);
|
||||
}));
|
||||
|
||||
describe('provided schemas', () => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ComponentUsingInvalidProperty]});
|
||||
});
|
||||
|
||||
it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
|
||||
() => {
|
||||
expect(TestBed.createComponent(ComponentUsingInvalidProperty).componentInstance)
|
||||
.toBeAnInstanceOf(ComponentUsingInvalidProperty);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('per test modules', () => {
|
||||
it('should use set up providers',
|
||||
withModule(moduleConfig).inject([FancyService], (service: FancyService) => {
|
||||
expect(service.value).toEqual('real value');
|
||||
}));
|
||||
|
||||
it('should use set up directives and pipes', withModule(moduleConfig, () => {
|
||||
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
|
||||
const el = compFixture.debugElement;
|
||||
|
||||
compFixture.detectChanges();
|
||||
expect(el.children[0].properties['title']).toBe('transformed someValue');
|
||||
}));
|
||||
|
||||
it('should use set up library modules',
|
||||
withModule(moduleConfig).inject([SomeLibModule], (libModule: SomeLibModule) => {
|
||||
expect(libModule).toBeAnInstanceOf(SomeLibModule);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('components with template url', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({declarations: [CompWithUrlTemplate]});
|
||||
TestBed.compileComponents();
|
||||
}));
|
||||
|
||||
it('should allow to createSync components with templateUrl after explicit async compilation',
|
||||
() => {
|
||||
const fixture = TestBed.createComponent(CompWithUrlTemplate);
|
||||
expect(fixture.nativeElement).toHaveText('from external template\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('overwriting metadata', () => {
|
||||
@Pipe({name: 'undefined'})
|
||||
class SomePipe {
|
||||
transform(value: string): string { return `transformed ${value}`; }
|
||||
}
|
||||
|
||||
@Directive({selector: '[undefined]'})
|
||||
class SomeDirective {
|
||||
someProp = 'hello';
|
||||
}
|
||||
|
||||
@Component({selector: 'comp', template: 'someText'})
|
||||
class SomeComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'comp', template: 'someOtherText'})
|
||||
class SomeOtherComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeComponent, SomeDirective, SomePipe]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({imports: [SomeModule]}));
|
||||
|
||||
describe('module', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.overrideModule(SomeModule, {set: {declarations: [SomeOtherComponent]}});
|
||||
});
|
||||
it('should work', () => {
|
||||
expect(TestBed.createComponent(SomeOtherComponent).componentInstance)
|
||||
.toBeAnInstanceOf(SomeOtherComponent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('component', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.overrideComponent(
|
||||
SomeComponent, {set: {selector: 'comp', template: 'newText'}});
|
||||
});
|
||||
it('should work', () => {
|
||||
expect(TestBed.createComponent(SomeComponent).nativeElement).toHaveText('newText');
|
||||
});
|
||||
});
|
||||
|
||||
describe('directive', () => {
|
||||
beforeEach(() => {
|
||||
TestBed
|
||||
.overrideComponent(
|
||||
SomeComponent, {set: {selector: 'comp', template: `<div someDir></div>`}})
|
||||
.overrideDirective(
|
||||
SomeDirective, {set: {selector: '[someDir]', host: {'[title]': 'someProp'}}});
|
||||
});
|
||||
it('should work', () => {
|
||||
const compFixture = TestBed.createComponent(SomeComponent);
|
||||
compFixture.detectChanges();
|
||||
expect(compFixture.debugElement.children[0].properties['title']).toEqual('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipe', () => {
|
||||
beforeEach(() => {
|
||||
TestBed
|
||||
.overrideComponent(
|
||||
SomeComponent, {set: {selector: 'comp', template: `{{'hello' | somePipe}}`}})
|
||||
.overridePipe(SomePipe, {set: {name: 'somePipe'}});
|
||||
});
|
||||
it('should work', () => {
|
||||
const compFixture = TestBed.createComponent(SomeComponent);
|
||||
compFixture.detectChanges();
|
||||
expect(compFixture.nativeElement).toHaveText('transformed hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
let testBedSpy: any;
|
||||
beforeEach(() => {
|
||||
testBedSpy = spyOn(getTestBed(), 'overrideComponent').and.callThrough();
|
||||
TestBed.overrideTemplate(SomeComponent, 'newText');
|
||||
});
|
||||
it(`should override component's template`, () => {
|
||||
const fixture = TestBed.createComponent(SomeComponent);
|
||||
expect(fixture.nativeElement).toHaveText('newText');
|
||||
expect(testBedSpy).toHaveBeenCalledWith(SomeComponent, {
|
||||
set: {template: 'newText', templateUrl: null}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setting up the compiler', () => {
|
||||
|
||||
describe('providers', () => {
|
||||
beforeEach(() => {
|
||||
const resourceLoaderGet = jasmine.createSpy('resourceLoaderGet')
|
||||
.and.returnValue(Promise.resolve('Hello world!'));
|
||||
TestBed.configureTestingModule({declarations: [CompWithUrlTemplate]});
|
||||
TestBed.configureCompiler(
|
||||
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
|
||||
});
|
||||
|
||||
it('should use set up providers', fakeAsync(() => {
|
||||
TestBed.compileComponents();
|
||||
tick();
|
||||
const compFixture = TestBed.createComponent(CompWithUrlTemplate);
|
||||
expect(compFixture.nativeElement).toHaveText('Hello world!');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('useJit true', () => {
|
||||
beforeEach(() => TestBed.configureCompiler({useJit: true}));
|
||||
it('should set the value into CompilerConfig',
|
||||
inject([CompilerConfig], (config: CompilerConfig) => {
|
||||
expect(config.useJit).toBe(true);
|
||||
}));
|
||||
});
|
||||
describe('useJit false', () => {
|
||||
beforeEach(() => TestBed.configureCompiler({useJit: false}));
|
||||
it('should set the value into CompilerConfig',
|
||||
inject([CompilerConfig], (config: CompilerConfig) => {
|
||||
expect(config.useJit).toBe(false);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
let originalJasmineIt: (description: string, func: () => void) => jasmine.Spec;
|
||||
let originalJasmineBeforeEach: (beforeEachFunction: () => void) => void;
|
||||
|
||||
const patchJasmineIt = () => {
|
||||
let resolve: (result: any) => void;
|
||||
let reject: (error: any) => void;
|
||||
const promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
originalJasmineIt = jasmine.getEnv().it;
|
||||
jasmine.getEnv().it = (description: string, fn: (done: DoneFn) => void) => {
|
||||
const done = <DoneFn>(() => resolve(null));
|
||||
done.fail = (err) => reject(err);
|
||||
fn(done);
|
||||
return null;
|
||||
};
|
||||
return promise;
|
||||
};
|
||||
|
||||
const restoreJasmineIt = () => jasmine.getEnv().it = originalJasmineIt;
|
||||
|
||||
const patchJasmineBeforeEach = () => {
|
||||
let resolve: (result: any) => void;
|
||||
let reject: (error: any) => void;
|
||||
const promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
|
||||
jasmine.getEnv().beforeEach = (fn: (done: DoneFn) => void) => {
|
||||
const done = <DoneFn>(() => resolve(null));
|
||||
done.fail = (err) => reject(err);
|
||||
fn(done);
|
||||
};
|
||||
return promise;
|
||||
};
|
||||
|
||||
const restoreJasmineBeforeEach = () => jasmine.getEnv().beforeEach =
|
||||
originalJasmineBeforeEach;
|
||||
|
||||
it('should fail when an asynchronous error is thrown', (done) => {
|
||||
const itPromise = patchJasmineIt();
|
||||
const barError = new Error('bar');
|
||||
|
||||
it('throws an async error',
|
||||
async(inject([], () => setTimeout(() => { throw barError; }, 0))));
|
||||
|
||||
itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => {
|
||||
expect(err).toEqual(barError);
|
||||
done();
|
||||
});
|
||||
restoreJasmineIt();
|
||||
});
|
||||
|
||||
it('should fail when a returned promise is rejected', (done) => {
|
||||
const itPromise = patchJasmineIt();
|
||||
|
||||
it('should fail with an error from a promise', async(inject([], () => {
|
||||
let reject: (error: any) => void;
|
||||
const promise = new Promise((_, rej) => reject = rej);
|
||||
const p = promise.then(() => expect(1).toEqual(2));
|
||||
|
||||
reject('baz');
|
||||
return p;
|
||||
})));
|
||||
|
||||
itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => {
|
||||
expect(err.message).toEqual('Uncaught (in promise): baz');
|
||||
done();
|
||||
});
|
||||
restoreJasmineIt();
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
let resourceLoaderGet: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
resourceLoaderGet = jasmine.createSpy('resourceLoaderGet')
|
||||
.and.returnValue(Promise.resolve('Hello world!'));
|
||||
TestBed.configureCompiler(
|
||||
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
|
||||
});
|
||||
|
||||
it('should report an error for declared components with templateUrl which never call TestBed.compileComponents',
|
||||
() => {
|
||||
const itPromise = patchJasmineIt();
|
||||
|
||||
expect(
|
||||
() =>
|
||||
it('should fail', withModule(
|
||||
{declarations: [CompWithUrlTemplate]},
|
||||
() => TestBed.createComponent(CompWithUrlTemplate))))
|
||||
.toThrowError(
|
||||
`This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
|
||||
`Please call "TestBed.compileComponents" before your test.`);
|
||||
|
||||
restoreJasmineIt();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should error on unknown bound properties on custom elements by default', () => {
|
||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||
class ComponentUsingInvalidProperty {
|
||||
}
|
||||
|
||||
const itPromise = patchJasmineIt();
|
||||
|
||||
expect(
|
||||
() => it(
|
||||
'should fail', withModule(
|
||||
{declarations: [ComponentUsingInvalidProperty]},
|
||||
() => TestBed.createComponent(ComponentUsingInvalidProperty))))
|
||||
.toThrowError(/Can't bind to 'someUnknownProp'/);
|
||||
|
||||
restoreJasmineIt();
|
||||
});
|
||||
});
|
||||
|
||||
describe('creating components', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
ChildComp,
|
||||
MyIfComp,
|
||||
ChildChildComp,
|
||||
ParentComp,
|
||||
TestProvidersComp,
|
||||
TestViewProvidersComp,
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should instantiate a component with valid DOM', async(() => {
|
||||
const fixture = TestBed.createComponent(ChildComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('Original Child');
|
||||
}));
|
||||
|
||||
it('should allow changing members of the component', async(() => {
|
||||
const componentFixture = TestBed.createComponent(MyIfComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('MyIf()');
|
||||
|
||||
componentFixture.componentInstance.showMore = true;
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('MyIf(More)');
|
||||
}));
|
||||
|
||||
it('should override a template', async(() => {
|
||||
TestBed.overrideComponent(ChildComp, {set: {template: '<span>Mock</span>'}});
|
||||
const componentFixture = TestBed.createComponent(ChildComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('Mock');
|
||||
|
||||
}));
|
||||
|
||||
it('should override a provider', async(() => {
|
||||
TestBed.overrideComponent(
|
||||
TestProvidersComp,
|
||||
{set: {providers: [{provide: FancyService, useClass: MockFancyService}]}});
|
||||
const componentFixture = TestBed.createComponent(TestProvidersComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('injected value: mocked out value');
|
||||
}));
|
||||
|
||||
|
||||
it('should override a viewProvider', async(() => {
|
||||
TestBed.overrideComponent(
|
||||
TestViewProvidersComp,
|
||||
{set: {viewProviders: [{provide: FancyService, useClass: MockFancyService}]}});
|
||||
|
||||
const componentFixture = TestBed.createComponent(TestViewProvidersComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('injected value: mocked out value');
|
||||
}));
|
||||
});
|
||||
describe('using alternate components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MockChildComp,
|
||||
ParentComp,
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should override component dependencies', async(() => {
|
||||
|
||||
const componentFixture = TestBed.createComponent(ParentComp);
|
||||
componentFixture.detectChanges();
|
||||
expect(componentFixture.nativeElement).toHaveText('Parent(Mock)');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user