_屬性指令_會修改元素、元件或其他指令的行為。 它的名稱反映了指令的套用方式:作為宿主元素上的一個屬性。
測試 Highlight 指令
範例應用程式的 Highlight 指令會根據資料繫結的顏色或預設顏色(lightgray),設定元素的背景顏色。
它也會將元素的自訂屬性(customProperty)設定為 true,理由僅僅是為了展示它可以這麼做。
import {Directive, inject, input} from '@angular/core';
/**
* Set backgroundColor for the attached element to highlight color
* and set the element's customProperty attribute to true
*/
@Directive({
selector: '[highlight]',
host: {
'[style.backgroundColor]': 'bgColor() || defaultColor',
},
})
export class Highlight {
readonly defaultColor = 'rgb(211, 211, 211)'; // lightgray
readonly bgColor = input('', {alias: 'highlight'});
}
它在整個應用程式中都有使用,或許最簡單的用法是在 About 元件中:
@Component({
imports: [Twain, Highlight],
template: `
<h2 highlight="skyblue">About</h2>
<h3>Quote of the day:</h3>
<twain-quote />
`,
})
export class About {}
在 About 元件中測試 Highlight 指令的特定用法,只需要元件測試情境中「巢狀元件測試」一節所探討的技巧。
let fixture: ComponentFixture<About>;
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [TwainService, UserService],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
});
fixture = TestBed.createComponent(About);
await fixture.whenStable();
});
it('should have skyblue <h2>', () => {
const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
const bgColor = h2.style.backgroundColor;
expect(bgColor).toBe('skyblue');
});
不過,只測試單一使用案例,不太可能探索到指令能力的完整範圍。 找出並測試所有使用該指令的元件既繁瑣又脆弱,而且同樣不太可能提供完整覆蓋率。
_僅類別測試_可能有幫助,但像這樣的屬性指令往往會操作 DOM。 隔離的單元測試不會觸及 DOM,因此無法讓人對指令是否真的有效產生信心。
更好的解決方案是建立一個人造的測試元件,展示套用該指令的所有方式。
@Component({
imports: [Highlight],
template: `
<h2 highlight="yellow">Something Yellow</h2>
<h2 highlight>The Default (Gray)</h2>
<h2>No Highlight</h2>
<input #box [highlight]="box.value" value="cyan" />
`,
})
class Test {}
HELPFUL: <input> 案例將 Highlight 繫結到輸入框中顏色值的名稱。
初始值是 "cyan" 這個字,應該會成為輸入框的背景顏色。
以下是這個元件的一些測試:
let fixture: ComponentFixture<Test>;
let des: DebugElement[]; // the three elements w/ the directive
beforeEach(async () => {
fixture = TestBed.createComponent(Test);
await fixture.whenStable();
// all elements with an attached Highlight
des = fixture.debugElement.queryAll(By.directive(Highlight));
});
// color tests
it('should have three highlighted elements', () => {
expect(des.length).toBe(3);
});
it('should color 1st <h2> background "yellow"', () => {
const bgColor = des[0].nativeElement.style.backgroundColor;
expect(bgColor).toBe('yellow');
});
it('should color 2nd <h2> background w/ default color', () => {
const dir = des[1].injector.get(Highlight);
const bgColor = des[1].nativeElement.style.backgroundColor;
expect(bgColor).toBe(dir.defaultColor);
});
it('should bind <input> background to value color', async () => {
// easier to work with nativeElement
const input = des[2].nativeElement as HTMLInputElement;
expect(input.style.backgroundColor, 'initial backgroundColor').toBe('cyan');
input.value = 'green';
// Dispatch a DOM event so that Angular responds to the input value change.
input.dispatchEvent(new Event('input'));
await fixture.whenStable();
expect(input.style.backgroundColor, 'changed backgroundColor').toBe('green');
});
it('bare <h2> should not have a backgroundColor', () => {
// the h2 without the Highlight directive
const bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
expect(bareH2.styles.backgroundColor).toBeUndefined();
});
有幾個技巧值得注意:
當元素型別未知時,
By.directive謂詞是取得帶有此指令之元素的絕佳方式By.css('h2:not([highlight])')中的:not虛擬類別有助於找出_沒有_該指令的<h2>元素。By.css('*:not([highlight])')會找出_任何_沒有該指令的元素。即使沒有真正的瀏覽器,
DebugElement.styles也能透過DebugElement抽象層存取元素樣式。 但當nativeElement看起來比抽象層更容易或更清楚時,也請自由利用它。Angular 會將指令加入它所套用元素的注入器中。 預設顏色的測試使用第二個
<h2>的注入器,取得它的Highlight執行個體與其defaultColor。DebugElement.properties可以存取由指令設定的人造自訂屬性
隔離測試指令
你可以用 TestBed.createDirective() 單獨建立一個指令,它會回傳 DirectiveFixture。或者,你也可以透過元件的範本來渲染它。
Highlight 指令可以用這種方式測試,使用本地測試元件的輸入來控制該指令。
@Component({
imports: [Highlight],
template: `<p [highlight]="color()">{{ color() }}</p>`,
})
class Test {
readonly color = input('');
}
describe('Highlight', () => {
let fixture: ComponentFixture<Test>;
beforeEach(async () => {
fixture = TestBed.createComponent(Test);
await fixture.whenStable();
});
it('should use the specified color once an input is provided', async () => {
fixture.componentRef.setInput('color', 'blue');
await fixture.whenStable();
const p = fixture.nativeElement.querySelector('p');
expect(p.style.backgroundColor).toBe('blue');
});
});