Skip to content

Writing Tests ​

Overview ​

This guide covers how to write effective tests for Nuxt SmartScript, including patterns, best practices, and common scenarios.

Test Structure ​

Basic Test Structure ​

typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'

describe('Feature Name', () => {
  // Setup before each test
  beforeEach(() => {
    // Initialize test environment
  })

  // Cleanup after each test
  afterEach(() => {
    // Clean up resources
  })

  // Group related tests
  describe('specific behavior', () => {
    it('should do something specific', () => {
      // Arrange
      const input = 'test data'
      
      // Act
      const result = functionUnderTest(input)
      
      // Assert
      expect(result).toBe('expected output')
    })
  })
})

Choosing the Right Test Type ​

Unit Test ​

Write a unit test when testing:

  • Individual functions
  • Pattern matching
  • Configuration logic
  • Utility functions
typescript
// test/unit/patterns.test.ts
it('should match trademark pattern', () => {
  const pattern = /\(TM\)/g
  expect('Product(TM)'.match(pattern)).toEqual(['(TM)'])
})

Integration Test ​

Write an integration test when testing:

  • Multiple components together
  • Processing pipeline
  • DOM manipulation with processing
  • Configuration flow
typescript
// test/integration/pattern-processing.test.ts
it('should process multiple patterns together', () => {
  const element = createTestElement('H2O at 25°C')
  engine.processElement(element)
  expect(element.querySelectorAll('sub, sup')).toHaveLength(2)
})

E2E Test ​

Write an E2E test when testing:

  • Browser-specific behavior
  • CSS rendering
  • Hydration
  • User interactions
typescript
// test/e2e/positioning.test.ts
test('CSS positioning in browser', async ({ page }) => {
  await page.goto('/')
  const tm = await page.locator('.ss-tm').first()
  const top = await tm.evaluate(el => getComputedStyle(el).top)
  expect(top).toBe('-0.4em')
})

Common Test Patterns ​

Pattern Matching Tests ​

typescript
describe('Pattern: Trademark', () => {
  const pattern = createPatterns().trademark

  // Test valid matches
  it.each([
    ['Product(TM)', ['(TM)']],
    ['Item(TM) and Another(TM)', ['(TM)', '(TM)']],
    ['END(TM)', ['(TM)']],
  ])('should match %s', (input, expected) => {
    expect(input.match(pattern)).toEqual(expected)
  })

  // Test invalid cases
  it.each([
    '(T M)',      // Space in middle
    '(tm)',       // Lowercase
    'TM',         // No parentheses
    '(TMM)',      // Extra character
  ])('should not match %s', (input) => {
    expect(input.match(pattern)).toBeNull()
  })
})

DOM Manipulation Tests ​

typescript
describe('DOM manipulation', () => {
  let container: HTMLElement

  beforeEach(() => {
    setupDOM()
    container = document.createElement('div')
    document.body.appendChild(container)
  })

  afterEach(() => {
    container.remove()
    cleanupDOM()
  })

  it('should preserve element structure', () => {
    container.innerHTML = '<p>H2O in <strong>bold</strong></p>'
    processElement(container)
    
    // Check structure preserved
    expect(container.querySelector('strong')).toBeTruthy()
    expect(container.querySelector('sub')).toBeTruthy()
  })
})

Configuration Tests ​

typescript
describe('Configuration', () => {
  it('should merge configurations correctly', () => {
    const base = { debug: false, performance: { debounce: 100 } }
    const user = { debug: true }
    const merged = mergeConfig(base, user)
    
    expect(merged).toEqual({
      debug: true,  // User override
      performance: { debounce: 100 }  // Base default
    })
  })

  it('should validate configuration', () => {
    const invalid = { performance: { debounce: -1 } }
    expect(() => validateConfig(invalid))
      .toThrow('Debounce must be positive')
  })
})

Async/Promise Tests ​

typescript
describe('Async operations', () => {
  it('should process after delay', async () => {
    const element = createTestElement('(TM)')
    const promise = processWithDelay(element, 100)
    
    // Not processed yet
    expect(element.querySelector('.ss-tm')).toBeFalsy()
    
    // Wait for processing
    await promise
    
    // Now processed
    expect(element.querySelector('.ss-tm')).toBeTruthy()
  })

  it('should handle errors gracefully', async () => {
    const element = createTestElement('invalid')
    
    await expect(processElement(element))
      .rejects.toThrow('Processing failed')
  })
})

Testing Edge Cases ​

Empty/Null Values ​

typescript
describe('Edge cases', () => {
  it.each([
    null,
    undefined,
    '',
    '   ',  // Whitespace only
  ])('should handle %s input', (input) => {
    expect(() => processText(input)).not.toThrow()
  })
})

Large Data Sets ​

typescript
it('should handle large documents efficiently', () => {
  const largeText = 'H2O '.repeat(10000)
  const element = createTestElement(largeText)
  
  const start = performance.now()
  processElement(element)
  const duration = performance.now() - start
  
  expect(duration).toBeLessThan(1000)  // Under 1 second
  expect(element.querySelectorAll('sub')).toHaveLength(10000)
})

Special Characters ​

typescript
it('should handle special characters', () => {
  const special = '™®©℃°µ¹²³'
  const element = createTestElement(special)
  
  expect(() => processElement(element)).not.toThrow()
})

Mocking and Stubbing ​

Mock External Dependencies ​

typescript
import { vi } from 'vitest'

// Mock console
vi.mock('consola', () => ({
  default: {
    debug: vi.fn(),
    error: vi.fn(),
    warn: vi.fn(),
  }
}))

// Mock module
vi.mock('../src/runtime/smartscript/logger', () => ({
  log: vi.fn(),
  error: vi.fn(),
}))

Spy on Functions ​

typescript
it('should call process method', () => {
  const spy = vi.spyOn(engine, 'process')
  
  engine.processElement(element)
  
  expect(spy).toHaveBeenCalledTimes(1)
  expect(spy).toHaveBeenCalledWith(element)
  
  spy.mockRestore()
})

Test Data Builders ​

Create reusable test data:

typescript
// test/helpers/builders.ts
export function buildTestDocument(options = {}) {
  return {
    scientific: 'H2O reacts with CO2',
    math: 'x^2 + y^2 = r^2',
    business: 'Product(TM) by Company(R)',
    mixed: 'All of the above at 25°C',
    ...options
  }
}

export function buildTestConfig(overrides = {}) {
  return {
    debug: false,
    performance: {
      debounce: 100,
      batchSize: 50,
    },
    ...overrides
  }
}

Assertions Best Practices ​

Use Specific Assertions ​

typescript
// Good ✅
expect(element.tagName).toBe('SPAN')
expect(count).toBeGreaterThan(0)
expect(array).toHaveLength(3)

// Less specific ❌
expect(element.tagName).toBeTruthy()
expect(count > 0).toBe(true)
expect(array.length === 3).toBe(true)

Custom Matchers ​

typescript
// test/helpers/matchers.ts
expect.extend({
  toBeProcessed(element: HTMLElement) {
    const processed = element.querySelector('.ss-sup, .ss-sub')
    return {
      pass: processed !== null,
      message: () => `Expected element to be processed`
    }
  }
})

// Usage
expect(element).toBeProcessed()

Test Organization ​

Group by Feature ​

typescript
describe('Trademark processing', () => {
  describe('pattern matching', () => {
    // Pattern tests
  })
  
  describe('transformation', () => {
    // Transformation tests
  })
  
  describe('rendering', () => {
    // Rendering tests
  })
})

Use Descriptive Names ​

typescript
// Good ✅
it('should create SPAN element for trademark symbols to allow CSS positioning')

// Bad ❌
it('should work')
it('test 1')
it('handles TM')

Performance Testing ​

typescript
import { bench, describe } from 'vitest'

describe('Performance', () => {
  bench('process 1000 trademarks', () => {
    const text = '(TM) '.repeat(1000)
    processText(text)
  })

  bench('process mixed content', () => {
    const text = 'H2O (TM) x^2 25°C '.repeat(100)
    processText(text)
  })
})

Debugging Tests ​

Debug Output ​

typescript
it('should debug', () => {
  const result = processText('input')
  
  // Temporary debug output
  console.log('Result:', result)
  console.log('Type:', typeof result)
  console.log('Properties:', Object.keys(result))
  
  expect(result).toBeDefined()
})

Step Through Debugger ​

bash
# Run with Node debugger
node --inspect-brk ./node_modules/.bin/vitest run

# Connect Chrome DevTools to chrome://inspect

Isolate Failing Test ​

bash
# Run only failing test
npx vitest run -t "specific test name"

# Run in watch mode for quick iteration
npx vitest watch path/to/test.ts

Apache 2.0 License