Testing Architecture
Overview
nuxt-smartscript uses a comprehensive multi-layer testing strategy to ensure reliability at every level of the stack. Each test layer serves a specific purpose and catches different types of bugs.
Why Multiple Test Layers?
Think of our testing strategy like testing a car:
- Unit tests = testing individual parts (spark plugs, pistons)
- SSR tests = testing the engine works on a test bench
- Nitro tests = testing the engine works when installed in the car
- Integration tests = testing subsystems work together
- E2E tests = driving the complete car on the road
Each layer can have its own bugs, so we test at each level!
Why We Need Both SSR and Nitro Tests
This is a common question since both test similar transformations (trademark, chemicals, etc.). The overlap is intentional because we're testing the same features at different layers:
SSR tests verify our jsdom approach works correctly - that we can use the same DOM-based code on the server
- Question: "Does the transformation work with jsdom?"
- Focus: Core algorithm correctness with server-side DOM
Nitro tests verify the plugin integrates properly with Nuxt's server-side rendering pipeline
- Question: "Does the plugin correctly apply transformations during server rendering?"
- Focus: Integration with Nuxt's SSR infrastructure
The same transformation might work perfectly in isolation (SSR test passes) but fail when integrated into Nuxt (Nitro test fails) due to:
- Configuration not being read correctly
- HTML chunks not being processed properly
- Hook registration issues
- Event context problems
Test Layers Explained
1. Unit Tests (test/unit/*.test.ts
)
What they test: Individual functions in isolation
Car analogy: Testing individual parts like spark plugs or pistons
Examples:
patterns.test.ts
- Tests regex patterns in isolationprocessor.test.ts
- Tests text processing logicdom.test.ts
- Tests DOM element creationengine.test.ts
- Tests client-side processing with mock DOM
When to use: When you want to verify a single function works correctly with various inputs
// Example: Testing a pattern in isolation
it('should match trademark symbols', () => {
expect('Product(TM)'.match(TRADEMARK_PATTERN)).toBeTruthy()
expect('TM alone'.match(TRADEMARK_PATTERN)).toBeFalsy()
})
2. SSR Tests (test/unit/ssr.test.ts
)
What they test: Core transformation logic using jsdom in a unit test environment
Car analogy: Testing the engine on a test bench
Key features:
- Directly imports and calls transformation functions
- Sets up jsdom manually to simulate server-side DOM
- Tests the same DOM manipulation code that runs on both client and server
// Directly tests the processing engine with jsdom
import { processElement } from '../../src/runtime/smartscript/engine'
const dom = new JSDOM('<p>Product(TM)</p>')
global.document = dom.window.document
processElement(element, config, patterns, pattern)
Tests include:
- Transformation algorithms work with jsdom
- Pattern matching correctness
- Exclusion zones (pre, code, data-no-superscript)
- Edge cases (standalone TM, H2O after uppercase)
- Double processing prevention
3. Integration Tests (test/integration/*.test.ts
)
What they test: Multiple components working together in a controlled environment
Car analogy: Testing the engine when installed in the chassis
Examples:
pattern-processing.test.ts
- Tests patterns + processor + DOM creation togetherelement-positioning.test.ts
- Tests CSS positioning with transformations
4. Nitro Plugin Tests (test/integration/nitro-plugin.test.ts
)
What they test: How transformations work within Nuxt's server-side rendering pipeline
Car analogy: Testing the engine while the car is on a dynamometer
Key features:
- Tests the actual Nitro plugin that hooks into Nuxt's SSR
- Simulates Nitro's hook system and event context
- Verifies configuration is read from runtime config
- Tests HTML chunk processing as Nitro provides it
// Tests the plugin integration with Nitro
const pluginModule = await import('../../src/runtime/nitro/plugin-jsdom')
nitroApp.hooks.hook('render:html', renderHook)
// Simulates how Nitro calls the plugin
renderHook(htmlChunks, { event: { context: { $config: {...} } } })
Tests include:
- Plugin registration with Nitro hooks
- Configuration reading from event context
- Multiple HTML body sections handling
- Error recovery for malformed HTML
- Full document wrapping
5. E2E Tests (test/e2e/*.test.ts
)
What they test: Complete module in a real Nuxt application
Car analogy: Driving the complete car on the road
Key features:
- Uses Playwright to test in real browsers
- Tests the full user experience
- Verifies both SSR and client-side hydration
- Tests dynamic content updates
// Tests in a real browser environment
test('transforms content correctly', async ({ page }) => {
await page.goto('/')
const content = await page.textContent('.content')
expect(content).toContain('™')
})
Why Multiple Test Layers?
Each layer can have its own unique bugs:
- Unit level: Algorithm might be wrong
- SSR level: jsdom compatibility issues
- Integration level: Components might not work together
- Nitro level: Plugin might not integrate with Nuxt properly
- E2E level: Client hydration might fail
Test Overlap is Intentional
You'll notice similar tests at different layers (e.g., both SSR and Nitro tests check trademark transformation). This is intentional:
- SSR tests ask: "Does the transformation work with jsdom?"
- Nitro tests ask: "Does the plugin correctly apply transformations during server rendering?"
- E2E tests ask: "Does the user see the correct result?"
Each question tests a different potential failure point.
Running Tests
# Run all tests
pnpm test
# Run specific test suites
npx vitest run test/unit
npx vitest run test/integration
npx vitest run test/e2e
# Run specific test file
npx vitest run test/unit/ssr.test.ts
# Run in watch mode
pnpm test:watch
# Run with coverage
pnpm test:coverage
Writing New Tests
Guidelines
- Start with unit tests for new functions
- Add integration tests when components need to work together
- Add SSR tests for server-side specific behavior
- Add Nitro tests for plugin integration changes
- Add E2E tests for user-facing features
Test File Organization
test/
├── unit/ # Isolated function tests
│ ├── ssr.test.ts # SSR-specific unit tests
│ └── regression/ # Tests for bug fixes
├── integration/ # Multi-component tests
│ └── nitro-plugin.test.ts # Nitro integration
├── e2e/ # Full application tests
├── fixtures/ # Test data and apps
└── helpers/ # Test utilities
Debugging Tests
# Run with debug output
DEBUG=* pnpm test
# Run single test
npx vitest run test/unit/ssr.test.ts -t "should transform trademark"
# Run with Node inspector
node --inspect-brk ./node_modules/.bin/vitest run
Performance Benchmarks
Performance tests (test/performance/*.test.ts
) ensure transformations remain fast:
// Benchmark example
it('should process 1000 nodes quickly', () => {
const start = performance.now()
processLargeDocument()
const duration = performance.now() - start
expect(duration).toBeLessThan(100) // Under 100ms
})
Continuous Integration
All tests run automatically on:
- Pull requests
- Commits to main branch
- Before npm publish
See .github/workflows/ci.yml
for CI configuration.