테스트 전략

모델 기반 contenteditable 에디터를 위한 포괄적인 테스트 전략입니다.

개요

contenteditable 에디터 테스트는 브라우저 차이, IME 동작, DOM 복잡성으로 인해 어렵습니다. 이 가이드는 다양한 수준의 테스트 전략을 다룹니다.

테스트 도전과제

contenteditable 에디터 테스트의 주요 도전과제:

  • 이벤트 발생 및 DOM 동작의 브라우저 차이
  • IME 조합 이벤트가 브라우저 간 일관되게 발생하지 않음
  • 브라우저 간 선택 API 차이
  • 모바일 브라우저가 데스크톱과 다른 동작
  • 비동기 작업 및 DOM 업데이트의 타이밍 문제

단위 테스트

모델 작업과 변환을 격리하여 테스트합니다.

모델 테스트

import { describe, it, expect } from 'vitest';
import { Editor } from './editor';

describe('모델 작업', () => {
  it('커서 위치에 텍스트 삽입', () => {
    const editor = new Editor();
    editor.setContent('Hello world');
    editor.setSelection({ start: 5, end: 5 });
    
    editor.insertText(' beautiful');
    
    expect(editor.getContent()).toBe('Hello beautiful world');
  });

  it('선택된 텍스트 삭제', () => {
    const editor = new Editor();
    editor.setContent('Hello world');
    editor.setSelection({ start: 0, end: 5 });
    
    editor.deleteContent();
    
    expect(editor.getContent()).toBe(' world');
  });
});

작업 테스트

describe('작업', () => {
  it('삽입 작업 적용', () => {
    const doc = { type: 'doc', children: [] };
    const operation = {
      type: 'insert',
      path: [0],
      node: { type: 'text', text: 'Hello' }
    };
    
    const result = applyOperation(doc, operation);
    
    expect(result.children[0].text).toBe('Hello');
  });

  it('실행 취소/다시 실행 지원', () => {
    const editor = new Editor();
    editor.insertText('Hello');
    editor.insertText(' world');
    
    editor.undo();
    expect(editor.getContent()).toBe('Hello');
    
    editor.redo();
    expect(editor.getContent()).toBe('Hello world');
  });
});

통합 테스트

DOM 동기화 및 이벤트 처리를 테스트합니다.

DOM 테스트

import { render, screen, fireEvent } from '@testing-library/react';

describe('DOM 동기화', () => {
  it('모델 변경 시 DOM 업데이트', () => {
    const { container } = render(<Editor />);
    const editor = container.querySelector('[contenteditable]');
    
    // 입력 시뮬레이션
    fireEvent.input(editor, {
      target: { textContent: 'Hello world' }
    });
    
    expect(editor.textContent).toBe('Hello world');
  });
});

이벤트 테스트

describe('이벤트 처리', () => {
  it('beforeinput 이벤트 처리', () => {
    const editor = new Editor();
    const element = editor.element;
    
    const handler = vi.fn();
    editor.on('beforeinput', handler);
    
    const event = new InputEvent('beforeinput', {
      inputType: 'insertText',
      data: 'Hello'
    });
    
    element.dispatchEvent(event);
    
    expect(handler).toHaveBeenCalled();
  });
});

IME 테스트

IME 테스트는 브라우저 차이로 인해 특별한 처리가 필요합니다.

조합 테스트

describe('IME 조합', () => {
  it('조합 이벤트 처리', async () => {
    const editor = new Editor();
    const element = editor.element;
    
    // 조합 시뮬레이션
    element.dispatchEvent(new CompositionEvent('compositionstart'));
    element.dispatchEvent(new CompositionEvent('compositionupdate', { data: '한' }));
    element.dispatchEvent(new CompositionEvent('compositionupdate', { data: '한글' }));
    element.dispatchEvent(new CompositionEvent('compositionend', { data: '한글' }));
    
    await new Promise(resolve => setTimeout(resolve, 100));
    
    expect(editor.getContent()).toContain('한글');
  });
});

크로스 브라우저 IME 테스트

참고: iOS Safari는 한국어 IME에 대해 조합 이벤트를 발생시키지 않습니다. 실제 기기나 브라우저 자동화 도구로 테스트하세요.

엔드투엔드 테스트

브라우저 자동화로 전체 사용자 워크플로우를 테스트합니다.

Playwright 테스트

import { test, expect } from '@playwright/test';

test('텍스트 삽입', async ({ page }) => {
  await page.goto('/editor');
  const editor = page.locator('[contenteditable]');
  
  await editor.click();
  await editor.type('Hello world');
  
  await expect(editor).toHaveText('Hello world');
});

test('IME 조합 처리', async ({ page }) => {
  await page.goto('/editor');
  const editor = page.locator('[contenteditable]');
  
  await editor.click();
  // 한국어 문자 입력
  await editor.pressSequentially('한글');
  
  await expect(editor).toContainText('한글');
});

모바일 테스트

모바일 테스트는 실제 기기나 에뮬레이션이 필요합니다.

test('가상 키보드 처리', async ({ page, isMobile }) => {
  if (!isMobile) test.skip();
  
  await page.goto('/editor');
  const editor = page.locator('[contenteditable]');
  
  await editor.tap();
  // 가상 키보드가 나타나야 함
  await editor.type('Hello');
  
  await expect(editor).toHaveText('Hello');
});

성능 테스트

대용량 문서로 에디터 성능을 테스트합니다.

test('대용량 문서 처리', async () => {
  const editor = new Editor();
  const largeText = 'Hello '.repeat(10000);
  
  const start = performance.now();
  editor.setContent(largeText);
  const end = performance.now();
  
  expect(end - start).toBeLessThan(100); // 100ms 미만으로 완료되어야 함
});

모범 사례

  • 모델 작업을 격리하여 테스트
  • DOM 동기화를 별도로 테스트
  • 조합 테스트에 실제 IME 사용
  • 여러 브라우저에서 테스트
  • 실제 모바일 기기에서 테스트
  • 외부 종속성 모킹
  • 엣지 케이스 및 오류 조건 테스트

Related Pages

에디터 아키텍처

에디터 아키텍처 개요

플러그인 개발

플러그인 개발 가이드

디버깅 기법

디버깅 전략