測試中的 DOM 元素檢查

在 Angular 應用的單元測試和整合測試中,我們經常需要檢查特定元素是否存在於 DOM 中、使用不同的選擇器查詢元素、以及驗證元素的樣式和內容。本篇筆記將詳細介紹這些 DOM 元素檢查技巧。

檢查元素是否存在於 DOM

在 Angular 測試中,我們需要頻繁地檢查某個元素是否存在於 DOM 中。這類檢查對於條件渲染的元素尤為重要。

檢查元素存在

it('應顯示標題元素', () => {
  fixture.detectChanges();
  
  // 方法 1:使用 fixture.debugElement
  const titleElement = fixture.debugElement.query(By.css('.title'));
  expect(titleElement).toBeTruthy();
  
  // 方法 2:使用 nativeElement
  const titleNative = fixture.nativeElement.querySelector('.title');
  expect(titleNative).not.toBeNull();
});

檢查元素不存在

it('錯誤訊息應該不顯示', () => {
  fixture.detectChanges();
  
  // 方法 1:使用 fixture.debugElement
  const errorElement = fixture.debugElement.query(By.css('.error-message'));
  expect(errorElement).toBeNull();
  
  // 方法 2:使用 nativeElement
  const errorNative = fixture.nativeElement.querySelector('.error-message');
  expect(errorNative).toBeNull();
});

檢查多個元素

it('應顯示 3 個選項', () => {
  fixture.detectChanges();
  
  // 方法 1:使用 queryAll
  const options = fixture.debugElement.queryAll(By.css('.option'));
  expect(options.length).toBe(3);
  
  // 方法 2:使用 nativeElement
  const optionsNative = fixture.nativeElement.querySelectorAll('.option');
  expect(optionsNative.length).toBe(3);
});

檢查是否可見

元素存在於 DOM 中不代表它是可見的。有時我們還需要檢查元素是否實際顯示:

it('tooltip 應在 hover 時可見', () => {
  // 觸發 hover 事件
  const button = fixture.debugElement.query(By.css('.info-button'));
  button.triggerEventHandler('mouseenter', {});
  fixture.detectChanges();
  
  // 檢查 tooltip 是否可見
  const tooltip = fixture.debugElement.query(By.css('.tooltip'));
  
  // 1. 檢查 display 屬性
  const display = window.getComputedStyle(tooltip.nativeElement).display;
  expect(display).not.toBe('none');
  
  // 2. 檢查可見性類
  expect(tooltip.nativeElement.classList).toContain('visible');
});

CSS 選擇器查詢技巧

在 Angular 測試中,我們可以使用不同的 CSS 選擇器來精確定位需要測試的元素。

基本選擇器

// 通過元素類型
const buttons = fixture.debugElement.queryAll(By.css('button'));

// 通過 CSS 類
const errorMessage = fixture.debugElement.query(By.css('.error-message'));

// 通過 ID
const userProfile = fixture.debugElement.query(By.css('#user-profile'));

// 通過屬性
const requiredInputs = fixture.debugElement.queryAll(By.css('[required]'));

複合選擇器

// 後代選擇器
const cardTitle = fixture.debugElement.query(By.css('.card .title'));

// 子元素選擇器
const listItems = fixture.debugElement.queryAll(By.css('ul > li'));

// 相鄰兄弟選擇器
const labelNextToInput = fixture.debugElement.query(By.css('input + label'));

// 屬性選擇器
const emailInput = fixture.debugElement.query(By.css('input[type="email"]'));

特殊選擇器技巧

// 包含特定文本的元素
const submitButton = fixture.debugElement.query(By.css('button:contains("提交")'));

// 第 n 個子元素
const secondListItem = fixture.debugElement.query(By.css('li:nth-child(2)'));

// 符合多個條件的元素
const activeCheckbox = fixture.debugElement.query(By.css('input[type="checkbox"].active:checked'));

使用 By.directive 通過指令查詢

除了 CSS 選擇器外,我們還可以通過 Angular 指令查詢元素:

import { MatButton } from '@angular/material/button';

// 查詢所有使用 MatButton 指令的元素
const matButtons = fixture.debugElement.queryAll(By.directive(MatButton));

檢查元素的樣式和 CSS 類

除了檢查元素是否存在,我們通常還需要驗證元素的樣式和 CSS 類是否符合預期。

檢查 CSS 類

it('錯誤狀態應該添加 error 類', () => {
  // 設置錯誤狀態
  component.hasError = true;
  fixture.detectChanges();
  
  const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
  
  // 檢查是否包含特定類
  expect(inputElement.classList).toContain('error');
  
  // 檢查是否包含多個類
  expect(inputElement.classList).toContain('form-control');
  expect(inputElement.classList).toContain('invalid');
  
  // 也可以這樣檢查
  expect(inputElement.className).toContain('error');
});

檢查行內樣式

it('應該設置正確的行內樣式', () => {
  component.highlightColor = 'red';
  fixture.detectChanges();
  
  const highlightedElement = fixture.debugElement.query(By.css('.highlight')).nativeElement;
  
  // 檢查行內樣式
  expect(highlightedElement.style.color).toBe('red');
  expect(highlightedElement.style.fontWeight).toBe('bold');
});

檢查計算樣式

有時我們需要檢查計算後的樣式,而不只是行內樣式:

it('元素應該有正確的計算樣式', () => {
  fixture.detectChanges();
  
  const element = fixture.debugElement.query(By.css('.styled-element')).nativeElement;
  const computedStyle = window.getComputedStyle(element);
  
  expect(computedStyle.display).toBe('flex');
  expect(computedStyle.marginTop).toBe('10px');
});

檢查 CSS 偽元素的內容

CSS 偽元素(如 :before:after)的內容檢查較為特殊,需要使用 getComputedStylegetPropertyValue

it('必填欄位應顯示星號', () => {
  fixture.detectChanges();
  
  const requiredLabel = fixture.debugElement.query(By.css('.required-field')).nativeElement;
  
  // 獲取 :after 偽元素的 content 屬性
  const afterContent = window.getComputedStyle(requiredLabel, ':after')
    .getPropertyValue('content');
    
  // 檢查內容是否為星號
  // 注意:content 值通常帶有引號,如 '"*"'
  expect(afterContent).toBe('"*"');
});

實用測試案例集

以下是一些實際的測試案例,展示如何在 Angular 測試中檢查 DOM 元素。

案例 1: 測試條件渲染

it('當 searchable 為 true 時應顯示搜尋框', () => {
  // 初始狀態
  fixture.detectChanges();
  let searchBox = fixture.debugElement.query(By.css('.search-input'));
  expect(searchBox).toBeNull(); // 預設不顯示
  
  // 設置為可搜尋
  component.searchable = true;
  fixture.detectChanges();
  
  searchBox = fixture.debugElement.query(By.css('.search-input'));
  expect(searchBox).toBeTruthy(); // 現在應該顯示
});

案例 2: 測試互動後的 DOM 變化

it('點擊切換按鈕應該顯示/隱藏內容', () => {
  fixture.detectChanges();
  
  // 初始狀態:內容應該隱藏
  let content = fixture.debugElement.query(By.css('.toggle-content'));
  expect(content).toBeNull();
  
  // 點擊切換按鈕
  const toggleButton = fixture.debugElement.query(By.css('.toggle-btn'));
  toggleButton.nativeElement.click();
  fixture.detectChanges();
  
  // 現在內容應該顯示
  content = fixture.debugElement.query(By.css('.toggle-content'));
  expect(content).toBeTruthy();
  
  // 再次點擊
  toggleButton.nativeElement.click();
  fixture.detectChanges();
  
  // 內容應再次隱藏
  content = fixture.debugElement.query(By.css('.toggle-content'));
  expect(content).toBeNull();
});

案例 3: 測試表單驗證錯誤訊息

it('當表單欄位無效時應顯示錯誤訊息', () => {
  fixture.detectChanges();
  
  // 獲取輸入欄位和錯誤訊息元素
  const emailInput = fixture.debugElement.query(By.css('input[formControlName="email"]'));
  
  // 初始狀態:應該無錯誤
  let errorMessage = fixture.debugElement.query(By.css('.email-error'));
  expect(errorMessage).toBeNull();
  
  // 設置無效的電子郵件
  emailInput.nativeElement.value = 'invalid-email';
  emailInput.nativeElement.dispatchEvent(new Event('input'));
  fixture.detectChanges();
  
  // 驗證錯誤訊息顯示
  errorMessage = fixture.debugElement.query(By.css('.email-error'));
  expect(errorMessage).toBeTruthy();
  expect(errorMessage.nativeElement.textContent).toContain('請輸入有效的電子郵件');
  
  // 修正為有效的電子郵件
  emailInput.nativeElement.value = 'valid@example.com';
  emailInput.nativeElement.dispatchEvent(new Event('input'));
  fixture.detectChanges();
  
  // 錯誤訊息應該消失
  errorMessage = fixture.debugElement.query(By.css('.email-error'));
  expect(errorMessage).toBeNull();
});

案例 4: 測試動態加載的下拉菜單 (Material Select)

it('點擊 mat-select 後應顯示可選項', async () => {
  fixture.detectChanges();
  
  // 初始狀態:選項面板不存在於 DOM
  let optionsPanel = document.querySelector('.mat-select-panel');
  expect(optionsPanel).toBeNull();
  
  // 點擊 select 以展開選項
  const select = fixture.debugElement.query(By.css('mat-select')).nativeElement;
  select.click();
  fixture.detectChanges();
  
  // 等待面板渲染
  await fixture.whenStable();
  
  // 現在應該能找到選項面板
  optionsPanel = document.querySelector('.mat-select-panel');
  expect(optionsPanel).toBeTruthy();
  
  // 檢查選項數量
  const options = document.querySelectorAll('.mat-option');
  expect(options.length).toBe(3); // 假設有 3 個選項
});

通過掌握這些 DOM 元素檢查技巧,你可以編寫更穩健、更全面的 Angular 測試,確保你的應用在各種情況下都能按預期運作。

Last updated