204 lines
6.8 KiB
TypeScript
204 lines
6.8 KiB
TypeScript
/**
|
|
* @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 {Component, Directive, ElementRef} from '@angular/core';
|
|
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
|
|
import {TestBed} from '@angular/core/testing';
|
|
import {DomSanitizer, SafeStyle} from '@angular/platform-browser';
|
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
|
import {onlyInIvy} from '@angular/private/testing';
|
|
|
|
describe('styling', () => {
|
|
beforeEach(ngDevModeResetPerfCounters);
|
|
|
|
it('should render inline style and class attribute values on the element before a directive is instantiated',
|
|
() => {
|
|
@Component({
|
|
template: `
|
|
<div directive-expecting-styling style="width:200px" class="abc xyz"></div>
|
|
`
|
|
})
|
|
class Cmp {
|
|
}
|
|
|
|
@Directive({selector: '[directive-expecting-styling]'})
|
|
class DirectiveExpectingStyling {
|
|
constructor(elm: ElementRef) {
|
|
const native = elm.nativeElement;
|
|
native.setAttribute('data-captured-width', native.style.width);
|
|
native.setAttribute('data-captured-classes', native.className);
|
|
}
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]});
|
|
const fixture = TestBed.createComponent(Cmp);
|
|
fixture.detectChanges();
|
|
|
|
const element = fixture.nativeElement.querySelector('div');
|
|
expect(element.style.width).toEqual('200px');
|
|
expect(element.getAttribute('data-captured-width')).toEqual('200px');
|
|
expect(element.className.trim()).toEqual('abc xyz');
|
|
expect(element.getAttribute('data-captured-classes')).toEqual('abc xyz');
|
|
});
|
|
|
|
it('should only render the same initial styling values once before a directive runs', () => {
|
|
@Component({
|
|
template: `
|
|
<div directive-expecting-styling style="width:200px" class="abc"></div>
|
|
`
|
|
})
|
|
class Cmp {
|
|
}
|
|
|
|
@Directive({selector: '[directive-expecting-styling]'})
|
|
class DirectiveExpectingStyling {
|
|
constructor(elm: ElementRef) {
|
|
const native = elm.nativeElement;
|
|
native.style.width = '300px';
|
|
native.classList.remove('abc');
|
|
}
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [Cmp, DirectiveExpectingStyling]});
|
|
const fixture = TestBed.createComponent(Cmp);
|
|
fixture.detectChanges();
|
|
|
|
const element = fixture.nativeElement.querySelector('div');
|
|
expect(element.style.width).toEqual('300px');
|
|
expect(element.classList.contains('abc')).toBeFalsy();
|
|
});
|
|
|
|
it('should ensure that static classes are assigned to ng-container elements and picked up for content projection',
|
|
() => {
|
|
@Component({
|
|
template: `
|
|
<project>
|
|
outer
|
|
<ng-container class="inner">
|
|
inner
|
|
</ng-container>
|
|
</project>
|
|
`
|
|
})
|
|
class MyApp {
|
|
}
|
|
|
|
@Component({
|
|
selector: 'project',
|
|
template: `
|
|
<div class="outer-area">
|
|
<ng-content></ng-content>
|
|
</div>
|
|
<div class="inner-area">
|
|
<ng-content select=".inner"></ng-content>
|
|
</div>
|
|
`
|
|
})
|
|
class ProjectCmp {
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [MyApp, ProjectCmp]});
|
|
const fixture = TestBed.createComponent(MyApp);
|
|
const element = fixture.nativeElement;
|
|
fixture.detectChanges();
|
|
|
|
const inner = element.querySelector('.inner-area');
|
|
expect(inner.textContent.trim()).toEqual('inner');
|
|
const outer = element.querySelector('.outer-area');
|
|
expect(outer.textContent.trim()).toEqual('outer');
|
|
});
|
|
|
|
it('should do nothing for empty style bindings', () => {
|
|
@Component({template: '<div [style.color]></div>'})
|
|
class App {
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [App]});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.innerHTML).toBe('<div></div>');
|
|
});
|
|
|
|
it('should do nothing for empty class bindings', () => {
|
|
@Component({template: '<div [class.is-open]></div>'})
|
|
class App {
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [App]});
|
|
const fixture = TestBed.createComponent(App);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.innerHTML).toBe('<div></div>');
|
|
});
|
|
|
|
it('should be able to bind a SafeValue to backgroundImage', () => {
|
|
@Component({template: '<div [style.backgroundImage]="image"></div>'})
|
|
class Cmp {
|
|
image !: SafeStyle;
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
|
const fixture = TestBed.createComponent(Cmp);
|
|
const sanitizer: DomSanitizer = TestBed.get(DomSanitizer);
|
|
|
|
fixture.componentInstance.image = sanitizer.bypassSecurityTrustStyle('url("#test")');
|
|
fixture.detectChanges();
|
|
|
|
const div = fixture.nativeElement.querySelector('div') as HTMLDivElement;
|
|
expect(div.style.backgroundImage).toBe('url("#test")');
|
|
|
|
onlyInIvy('perf counters').expectPerfCounters({
|
|
stylingApply: 2,
|
|
stylingApplyCacheMiss: 1,
|
|
stylingProp: 2,
|
|
stylingPropCacheMiss: 1,
|
|
tNode: 3,
|
|
});
|
|
});
|
|
|
|
it('should not throw if host style binding is on a template node', () => {
|
|
// This ex is a bit contrived. In real apps, you might have a shared class that is extended both
|
|
// by components with host elements and by directives on template nodes. In that case, the host
|
|
// styles for the template directives should just be ignored.
|
|
@Directive({selector: 'ng-template[styleDir]', host: {'[style.display]': 'display'}})
|
|
class StyleDir {
|
|
display = 'block';
|
|
}
|
|
|
|
@Component({selector: 'app-comp', template: `<ng-template styleDir></ng-template>`})
|
|
class MyApp {
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [MyApp, StyleDir]});
|
|
expect(() => {
|
|
const fixture = TestBed.createComponent(MyApp);
|
|
fixture.detectChanges();
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should be able to bind a SafeValue to clip-path', () => {
|
|
@Component({template: '<div [style.clip-path]="path"></div>'})
|
|
class Cmp {
|
|
path !: SafeStyle;
|
|
}
|
|
|
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
|
const fixture = TestBed.createComponent(Cmp);
|
|
const sanitizer: DomSanitizer = TestBed.get(DomSanitizer);
|
|
|
|
fixture.componentInstance.path = sanitizer.bypassSecurityTrustStyle('url("#test")');
|
|
fixture.detectChanges();
|
|
|
|
const html = fixture.nativeElement.innerHTML;
|
|
|
|
// Note that check the raw HTML, because (at the time of writing) the Node-based renderer
|
|
// that we use to run tests doesn't support `clip-path` in `CSSStyleDeclaration`.
|
|
expect(html).toMatch(/style=["|']clip-path:\s*url\(.*#test.*\)/);
|
|
});
|
|
});
|