Component & Unit Testing
Relevant source files
The following files were used as context for generating this wiki page:
The RoundingWell frontend employs a multi-tiered testing strategy to ensure software quality and reliability. This system encompasses localized unit tests for business logic, browser-based component tests for UI elements, and a centralized coverage merging system that enforces strict quality baselines.
The architecture separates concerns between headless unit testing (via Vitest) and functional UI testing (via Cypress). This ensures that low-level utilities are validated for correctness in isolation while complex view components and user flows are verified within a simulated browser environment.
Testing Architecture & Tooling
The testing environment is built on a modern JavaScript stack, utilizing specific runners for different levels of the application.
Core Testing Toolchain
| Tool | Purpose | Primary Scope |
|---|---|---|
| Vitest | Unit Runner | Pure functions, state logic, and utility modules. |
| Cypress | Component/Functional Runner | Marionette Views, UI components, and integrated features. |
| NYC/Istanbul | Coverage Tooling | Measuring code execution and merging disparate reports. |
| Mocha/Chai/Sinon | Testing Framework/Assertions | Standardized syntax for writing expectations and mocks. |
Sources: package.json:115-135, src/js/utils/README.md:99-105
Testing Workflow Diagram
This diagram illustrates how code changes flow through different testing layers before reaching the final coverage report.
Sources: test/README.md:10-18, package.json:43-52
Unit Testing Strategy
A "unit" is defined as a single .js file. The repository enforces a colocated structure where Vitest suites (*.test.js) live immediately next to their corresponding source files.
Design Principles
- Behavior over Implementation: Tests describe what the module does, not how it works internally.
- Isolation: Units should not have knowledge of how other units function; tests must not be coupled.
- Public API focus: Only public methods are tested. Testing private methods is discouraged as it breaks encapsulation.
- Pure Functions: Utility functions should ideally be pure (no side effects) and tested in isolation.
Sources: src/js/utils/README.md:10-40, test/README.md:21-25
Example: Formatting Utilities Unit Test
The following illustrates the testing of pure functions using Vitest.
describe('formatting', () => {
it('buildMatcher', () => {
const matcher = buildMatcher('test string');
expect(matcher).toEqual(/\btest|string/gi);
});
it('px', () => {
expect(px(25.25)).toBe('25.25px');
});
it('underscored', () => {
expect(underscored('foo bar-baz')).toBe('foo_bar_baz');
});
});
Sources: src/js/utils/formatting/formatting.test.js:17-21, 55-57, 73-76
Component & Functional Testing
Cypress is used for functional testing of the application through the User Interface. These tests are not "End-to-End" (E2E) in a traditional sense, as all server data is typically stubbed.
Component Test Structure
Browser/Component tests are colocated with source code using the *.cy.js extension. They utilize a specific organization pattern:
- Context: Defines the feature or app section.
- beforeEach: Sets up routes and initializes the app at a specific URL.
- specify: Executes specific data scenarios (e.g., empty results, null values, or specific user roles).
Sources: test/README.md:31-50, AGENTS.md:15-18
Synchronization and State
Tests must use observable state as the default synchronization strategy rather than arbitrary timeouts.
- Route Aliases: Prefer
cy.wait('@routeAlias')for network request lifecycles. - Retrying Assertions: Use
cy.get(...).should(...)to wait for DOM updates. - State Colors: Errors and states are verified using a centralized
state-colors.jsmap rather than hardcoded CSS classes.
Sources: test/README.md:73-88, 114-120
Coverage and Quality Guardrails
The repository enforces a strict 100% coverage baseline for statements, functions, lines, and branches.
Coverage Merging Process
Because coverage is generated by both Vitest and Cypress, a merging process is required to create a single source of truth.
Coverage Enforcement
Coverage is managed via several npm commands:
npm run coverage:check:baseline: Enforces the repo-wide 100% merged baseline.npm run coverage:check:migrated: Enforces 100% coverage for specific files migrated to Vitest, tracked intest/unit/coverage-owned-files.mjs.
Sources: test/README.md:13-18, package.json:48-49
Handling Path Normalization
A critical component of the testing infrastructure is normalizeCoveragePaths, which ensures that coverage collected in different environments (like local dev vs. CircleCI) can be merged correctly by rewriting absolute paths to use the current working directory.
it('rewrites absolute paths from a different prefix to use the current cwd', () => {
const foreignPath = '/root/project/src/js/app.js';
const normalized = normalizeCoveragePaths(coverage);
const expectedPath = path.resolve('src/js/app.js');
expect(Object.keys(normalized)).toEqual([expectedPath]);
});
Sources: test/unit/merge-coverage.test.js:84-100
Summary
The component and unit testing system at RoundingWell prioritizes business logic isolation and visual state verification. By utilizing colocated test files and enforcing a 100% coverage baseline through merged reporting, the project ensures that both low-level utilities and complex Marionette views are robust and maintainable.