| name | testing |
| description | Testing patterns, TDD workflow, TypeScript and Rust test conventions, sourcemap testing, E2E best practices, server cleanup for Verter |
Testing Patterns & Conventions
Server Cleanup
IMPORTANT: After starting any dev server, preview server, or other long-running process for testing purposes, always kill it when done. This prevents stale servers from interfering with subsequent test runs (e.g., Playwright's reuseExistingServer: true will use a stale server serving old builds).
kill $(lsof -t -i:4173)
taskkill //F //PID <pid>
Test Output Best Practices
When running E2E tests or test suites where you need to inspect output, redirect output to a temp file first, then grep/read the file. This avoids re-running expensive builds and tests just to search for different patterns:
pnpm exec playwright test --project=preview 2>&1 | tee /tmp/e2e-output.log
grep -i "fail\|error" /tmp/e2e-output.log
pnpm exec playwright test --project=preview 2>&1 | grep "fail"
pnpm exec playwright test --project=preview 2>&1 | grep "error"
TypeScript Test Patterns
Test locations: Unit tests are co-located as *.spec.ts next to source files. Type tests in packages/types/ use vitest --typecheck.
AI-generated tests: Add appropriate comments indicating AI assistance:
it("does something", () => {
});
Sourcemap testing (see macros.map.spec.ts):
const { s, source, result } = processMacrosForSourcemap(code);
const map = s.generateMap({ source: "test.vue" });
Type testing best practices (packages/types/):
- Always include both a positive assertion and a
@ts-expect-error negative assertion
- This prevents
any/unknown/never types from silently passing tests
it("type is correctly inferred", () => {
type Result = SomeTypeHelper<Input>;
assertType<Result>({} as ExpectedType);
assertType<ExpectedType>({} as Result);
assertType<{ unrelated: true }>({} as Result);
});
Rust Test Patterns
Test File Organization
When a Rust source file's inline #[cfg(test)] mod tests block exceeds ~400 lines, extract tests to a separate sibling file. Two patterns:
For standalone files (e.g., analysis.rs):
#[cfg(test)]
#[path = "analysis_tests.rs"]
mod analysis_tests;
For mod.rs files (e.g., ide/template/mod.rs):
#[cfg(test)]
mod tests;
The extracted file contains the module contents directly — use super::*;, helpers, and #[test] functions. No wrapping mod tests { } block.
TDD Workflow
- Write failing tests first
- Implement the minimum code to pass
- Run
cargo clippy --fix --allow-dirty --allow-staged --workspace -- -D warnings && cargo fmt --all
- Run
cargo test --package verter_core --lib
Arena-Based Template AST
The parser builds a flat Vec<AstNode> arena with O(1) navigation:
pub struct TemplateAst {
nodes: Vec<AstNode>,
root: RootNodeTemplate,
}
pub struct AstNode {
kind: AstNodeKind,
parent: Option<NodeId>,
index_in_parent: usize,
}
ElementNode pre-computes metadata during parsing to avoid re-scanning in codegen:
tag_type: Element / Component / SlotOutlet / Template
prop_flag: Bitset of prop characteristics (has class, style, spread, etc.)
children_flag: Bitset of children characteristics (has text, elements, v-if, etc.)
children_mode: Enum for codegen branching (Empty, TextOnly, SingleElement, Mixed, etc.)
- Cached directives:
v_condition, v_for, v_slot, v_once, v_ref
Test Validation Pattern
All codegen tests must validate generated JS syntax:
let result = compile_sfc(source);
let tpl = result.template.unwrap();
let parsed = oxc_parser::Parser::new(&alloc, &tpl.code, source_type).parse();
assert!(parsed.errors.is_empty(), "JS parse error: {:?}\n{}", parsed.errors, tpl.code);
Adding a New TS Plugin
- Create plugin file in
packages/core/src/v5/process/script/plugins/
- Implement
ScriptPlugin interface using definePlugin
- Register in
packages/core/src/v5/process/script/plugins/index.ts
- Add tests
Testing Strategy
- Unit tests: Test individual plugins with minimal SFC snippets
- Integration tests: Test full transformation pipeline
- Type tests: Verify TypeScript inference (using
vitest --typecheck)
- Sourcemap tests: Verify position mappings