A comprehensive testing framework for the Uniweb CLI tool that validates functionality, performance, and user experience through automated tests and exercise validation.
This testing framework provides:
- Comprehensive CLI Testing - Tests all commands and workflows
- Exercise Validation - Ensures documentation tutorials actually work
- Performance Monitoring - Catches performance regressions early
- User Experience Testing - Validates error messages and context awareness
- Fluent Test API - Makes writing and maintaining tests enjoyable
- Node.js 18+ installed
- Uniweb CLI installed globally:
npm install -g @uniwebcms/toolkit
# Clone this testing repository
git clone <your-test-repo-url>
cd uniweb-cli-tests
# Install dependencies
npm install# Check that Uniweb CLI is available
uniweb --version
# Run a quick test
npm run test:unit# Run all tests
npm test
# Run specific test categories
npm run test:unit
npm run test:integration
npm run test:exercises
# Run in watch mode during development
npm run test:watch
# Run with UI for visual debugging
npm run test:ui
# Validate that exercises work
npm run validate-exercises
# Run performance benchmarks
npm run benchmarktests/
├── setup/
│ ├── test-helpers.js # Base TestEnvironment class
│ ├── additional-helpers.js # Extensions and helper classes
│ └── enhanced-test-helpers.js # Simple loader that combines everything
├── unit/
│ └── init.test.js # Individual command tests
├── integration/
│ └── comprehensive-workflow.test.js # Full workflow tests
├── exercises/
│ └── exercise-validation.test.js # Exercise validation
└── performance/
└── benchmark.test.js # Performance tests
package.json # Dependencies and scripts
vitest.config.js # Test configuration
await env
.initProject("my-portfolio", { singleSite: true })
.addPage("about")
.addSection("hero", { page: "about" })
.setSection("hero", "# About Me\n\nContent here", { page: "about" });const content = env
.buildContent()
.component("HeroSection")
.param("theme", "dark")
.title("Welcome")
.paragraph("Great content")
.link("Get Started", "/start", { "button-primary": true })
.build();await env
.assert()
.fileExists("pages/index/hero.md")
.fileContains("pages/index/hero.md", "Welcome")
.yamlProperty("pages/index/page.yml", "sections", ["hero"])
.verify();const perf = env.performance();
await perf.timeOperation("page-creation", () => env.addPage("test"));
perf.expectOperationFasterThan("page-creation", 1000);await env
.batch()
.addPage("about")
.addSection("hero", { page: "about" })
.setSection("hero", content, { page: "about" })
.execute();Test individual CLI commands in isolation:
uniweb initwith various optionsuniweb addfor pages, sections, localesuniweb setfor content management- Error handling and validation
Test complete workflows and complex scenarios:
- Full content creation workflows
- Multi-site project management
- Multilingual content handling
- Component library integration
Validate that documentation tutorials work:
- Portfolio creation exercise
- Multi-language marketing site
- Component library development
- Real-world scenarios
Monitor CLI performance and catch regressions:
- Bulk operations (creating many pages/sections)
- Large project handling
- Command execution speed
- Memory usage
import { TestEnvironment } from "../setup/enhanced-test-helpers.js";
describe("My Test Suite", () => {
let env;
beforeEach(async () => {
env = new TestEnvironment();
await env.setup();
});
afterEach(async () => {
await env.cleanup();
});
it("should test something", async () => {
// Your test code here
});
});The testing framework provides a fluent API that makes tests readable and maintainable:
// Create a complete project workflow
await env
.initProject("my-portfolio", { singleSite: true })
.addPage("about")
.addPage("projects")
.addSection("hero", { page: "index" })
.addSection("skills", { page: "about" });
// Build rich content
const heroContent = env
.buildContent()
.component("HeroSection")
.param("layout", "centered")
.param("theme", "dark")
.title("Welcome to My Site")
.paragraph("I create amazing digital experiences")
.link("View Work", "/projects", { "button-primary": true })
.build();
// Set content efficiently
await env.setSection("hero", heroContent, { page: "index" });
// Verify results with assertion chaining
await env
.assert()
.fileExists("pages/index/hero.md")
.fileExists("pages/about/skills.md")
.fileContains("pages/index/hero.md", "Welcome to My Site")
.yamlProperty("pages/index/page.yml", "sections", ["hero"])
.verify();For efficiency when creating multiple resources:
await env
.batch()
.addPage("blog")
.addPage("about")
.addSection("hero", { page: "index" })
.addSection("posts", { page: "blog" })
.setSection("hero", welcomeContent, { page: "index" })
.execute();Use pre-built scenarios for common project types:
// Create a complete portfolio scenario
await env.scenarios().createPortfolioScenario();
// Create an e-commerce site with sample data
await env.scenarios().createEcommerceScenario();
// Create a blog with multilingual content
await env.scenarios().createBlogScenario();Monitor CLI performance:
const perf = env.performance();
// Time an operation
const { duration } = await perf.timeOperation("page-creation", async () => {
await env.addPage("test-page");
});
// Set performance expectations
perf.expectOperationFasterThan("page-creation", 1000); // Under 1 second
// Benchmark bulk operations
const metrics = await perf.benchmarkBulkOperations(50); // Create 50 pages
expect(metrics.avgTimePerOperation).toBeLessThan(100); // Under 100ms eachenv.initProject(name, options)- Initialize a new projectenv.addPage(name, options)- Add a pageenv.addSection(name, options)- Add a sectionenv.setSection(name, content, options)- Set section contentenv.addLocale(locales, options)- Add language support
env.navigateToProject(name)- Change to project directoryenv.navigateToSite(siteName)- Change to site directoryenv.navigateToPage(pageName)- Change to page directoryenv.cd(directory)- Change to any directory
env.expectFileExists(path)- Assert file existsenv.expectFileContains(path, content)- Assert file contains textenv.expectFilesExist(...paths)- Assert multiple files existenv.expectYamlProperty(path, property, value)- Assert YAML propertyenv.expectCommandSuccess(args)- Assert command succeedsenv.expectCommandFailure(args, error)- Assert command fails
env.buildContent()- Start building markdown content.component(name)- Set component.param(key, value)- Add parameter.title(text)- Add title.paragraph(text)- Add paragraph.heading(level, text)- Add heading.list(items)- Add list.image(src, alt)- Add image.link(text, url, attributes)- Add link.build()- Generate final content
env.expectStandardProjectStructure(type)- Verify project structureenv.expectStandardSiteStructure(path)- Verify site structureenv.expectProjectType(type)- Verify project matches type pattern
# Run all tests
npm test
# Run specific test categories
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
npm run test:exercises # Exercise validation only
npm run test:performance # Performance tests only
# Development and debugging
npm run test:watch # Watch mode for development
npm run test:ui # Visual test interface
npm run test:coverage # Generate coverage report
# Validation and reporting
npm run validate-exercises # Validate documentation exercises
npm run benchmark # Run performance benchmarks
npm run test:ci # CI-friendly test run# Run a specific test file
npx vitest tests/unit/init.test.js
# Run tests matching a pattern
npx vitest --grep "portfolio"
# Run tests with specific timeout
npx vitest --testTimeout=60000name: CLI Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install CLI
run: npm install -g @uniwebcms/toolkit
- name: Install test dependencies
run: npm install
- name: Run tests
run: npm run test:ci
- name: Validate exercises
run: npm run validate-exercisesCreate your own scenario builders:
// In your test file
class CustomScenarioBuilder extends ScenarioBuilder {
async createDocumentationSite() {
await this.env
.initProject("docs", { singleSite: true })
.addPage("api")
.addPage("guides")
.addPage("examples");
// Add structured content...
return this.env;
}
}
// Use in tests
const scenarios = new CustomScenarioBuilder(env);
await scenarios.createDocumentationSite();Test component library integration:
// Mock a component library
await env.mockComponentLibrary("ui-components", [
{ name: "HeroSection", category: "Layout" },
{ name: "FeatureGrid", category: "Content" },
{ name: "ContactForm", category: "Forms" },
]);
// Test using the components
const content = env
.buildContent()
.component("HeroSection")
.param("layout", "centered")
.title("Test Component")
.build();Test error handling and edge cases:
// Test invalid inputs
await env.expectCommandFailure(["add", "page", ""], "Invalid page name");
// Test missing dependencies
await env.expectCommandFailure(
["add", "section", "hero", "--page", "nonexistent"],
"Page not found"
);
// Test context-specific errors
await env.navigateToPage("test");
await env.expectCommandFailure(
["add", "section", "duplicate"],
"Section already exists"
);CLI Not Found
# Install CLI globally
npm install -g @uniwebcms/toolkit
# Verify installation
uniweb --versionTests Timing Out
# Increase timeout for slow operations
npx vitest --testTimeout=60000
# Or set in vitest.config.js
export default defineConfig({
test: {
timeout: 60000
}
})Permission Errors
# Ensure test cleanup is working
# Check beforeEach/afterEach in your tests
beforeEach(async () => {
env = new TestEnvironment()
await env.setup()
})
afterEach(async () => {
await env.cleanup() // This is crucial
})Enable verbose logging:
# Run with debug output
DEBUG=uniweb:* npm test
# Run specific test with full output
npx vitest tests/unit/init.test.js --reporter=verboseInspect test data during development:
it("should debug test data", async () => {
await env.initProject("debug-test", { singleSite: true });
// Log directory structure
const structure = await env.getDirectoryStructure();
console.log("Project structure:", JSON.stringify(structure, null, 2));
// Log file contents
const config = await env.readYaml("site.yml");
console.log("Site config:", config);
// Use the test UI for visual inspection
// npm run test:ui
});- Choose the right category (unit/integration/exercises/performance)
- Use existing patterns from similar tests
- Follow the fluent API for consistency
- Add performance expectations for new operations
- Test both success and failure cases
- Add to appropriate helper class in
additional-helpers.js - Follow the fluent API pattern (return
thisfor chaining) - Include error handling and validation
- Add JSDoc comments for complex functions
- Update this README with new helper documentation
- Use descriptive test names that explain what's being tested
- Keep tests focused - one concept per test
- Use setup helpers instead of repeating CLI commands
- Test edge cases and error conditions
- Add performance expectations for new operations
- Mock external dependencies appropriately
This testing framework is part of the Uniweb project and follows the same license terms.
For questions about the testing framework:
- Check this README first
- Look at existing test examples
- Review the helper function implementations
- Open an issue in the main Uniweb repository
Happy Testing! 🧪✨