aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
384 lines • 14.5 kB
JavaScript
/**
* TestCodeGenerator - Generate test code scaffolds from test cases
*
* Generates test file skeletons (unit, integration, E2E) implementing
* the arrange-act-assert pattern with TODO comments for manual completion.
*
* @module src/testing/generators/test-code-generator
*/
// ===========================
// TestCodeGenerator Class
// ===========================
export class TestCodeGenerator {
options;
constructor(options) {
this.options = {
includeSetup: true,
includeTeardown: true,
includeComments: true,
indentSize: 2,
generateMocks: true,
...options
};
}
/**
* Generate test files from a test suite
*
* @param suite - Test suite with test cases
* @returns Code generation result with generated files
*/
generate(suite) {
const warnings = [];
const files = [];
try {
// Group test cases by level
const unitTests = suite.testCases.filter(tc => tc.level === 'unit');
const integrationTests = suite.testCases.filter(tc => tc.level === 'integration');
const e2eTests = suite.testCases.filter(tc => tc.level === 'e2e');
// Generate unit test file
if (unitTests.length > 0) {
const unitFile = this.generateTestFile(unitTests, suite, 'unit');
files.push(unitFile);
}
// Generate integration test file
if (integrationTests.length > 0) {
const integrationFile = this.generateTestFile(integrationTests, suite, 'integration');
files.push(integrationFile);
}
// Generate E2E test file
if (e2eTests.length > 0) {
const e2eFile = this.generateE2ETestFile(e2eTests, suite);
files.push(e2eFile);
}
return {
success: true,
files,
errors: [],
warnings,
stats: {
totalFiles: files.length,
totalTests: suite.testCases.length,
unitTests: unitTests.length,
integrationTests: integrationTests.length,
e2eTests: e2eTests.length
}
};
}
catch (error) {
return {
success: false,
files,
errors: [`Code generation failed: ${error.message}`],
warnings,
stats: {
totalFiles: 0,
totalTests: 0,
unitTests: 0,
integrationTests: 0,
e2eTests: 0
}
};
}
}
/**
* Generate a test file for unit or integration tests
*/
generateTestFile(testCases, suite, level) {
const indent = ' '.repeat(this.options.indentSize);
const ext = this.options.language === 'typescript' ? '.ts' : '.js';
const filename = `${suite.useCaseId.toLowerCase()}.${level}.test${ext}`;
const lines = [];
// File header
lines.push(this.generateFileHeader(suite, level));
lines.push('');
// Imports
lines.push(this.generateImports(level));
lines.push('');
// Test suite
lines.push(`describe('${suite.name} - ${level.charAt(0).toUpperCase() + level.slice(1)} Tests', () => {`);
// Setup/teardown
if (this.options.includeSetup) {
lines.push(this.generateSetup(indent, suite));
}
if (this.options.includeTeardown) {
lines.push(this.generateTeardown(indent));
}
// Test cases
for (const testCase of testCases) {
lines.push('');
lines.push(this.generateTestCase(testCase, indent));
}
lines.push('});');
lines.push('');
return {
filename,
content: lines.join('\n'),
testLevel: level,
testCount: testCases.length
};
}
/**
* Generate an E2E test file (Playwright format)
*/
generateE2ETestFile(testCases, suite) {
const indent = ' '.repeat(this.options.indentSize);
const ext = this.options.language === 'typescript' ? '.ts' : '.js';
const filename = `${suite.useCaseId.toLowerCase()}.e2e.test${ext}`;
const lines = [];
// File header
lines.push(this.generateFileHeader(suite, 'e2e'));
lines.push('');
// Playwright imports
if (this.options.framework === 'playwright') {
lines.push("import { test, expect } from '@playwright/test';");
}
else {
lines.push(this.generateImports('e2e'));
}
lines.push('');
// Test suite
lines.push(`test.describe('${suite.name} - E2E Tests', () => {`);
// Setup
if (this.options.includeSetup) {
lines.push(`${indent}test.beforeEach(async ({ page }) => {`);
lines.push(`${indent}${indent}// TODO: Navigate to application and set up initial state`);
lines.push(`${indent}${indent}await page.goto('/');`);
lines.push(`${indent}});`);
lines.push('');
}
// Test cases
for (const testCase of testCases) {
lines.push(this.generateE2ETestCase(testCase, indent));
lines.push('');
}
lines.push('});');
lines.push('');
return {
filename,
content: lines.join('\n'),
testLevel: 'e2e',
testCount: testCases.length
};
}
/**
* Generate file header with metadata
*/
generateFileHeader(suite, _level) {
if (!this.options.includeComments)
return '';
return `/**
* ${_level.toUpperCase()} Tests: ${suite.name}
*
* Auto-generated from ${suite.useCaseId}
* Generated at: ${suite.generatedAt}
*
* @module test/${_level}/${suite.useCaseId.toLowerCase()}
*/`;
}
/**
* Generate imports based on framework
*/
generateImports(_level) {
switch (this.options.framework) {
case 'vitest':
return "import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';";
case 'jest':
return "import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';";
case 'mocha':
return "import { describe, it, before, after } from 'mocha';\nimport { expect } from 'chai';";
case 'playwright':
return "import { test, expect } from '@playwright/test';";
default:
return '';
}
}
/**
* Generate setup block
*/
generateSetup(indent, _suite) {
const lines = [];
const mockFn = this.options.framework === 'vitest' ? 'vi.fn()' : 'jest.fn()';
lines.push(`${indent}// Test fixtures`);
if (this.options.language === 'typescript') {
lines.push(`${indent}let testContext: Record<string, any>;`);
}
else {
lines.push(`${indent}let testContext;`);
}
lines.push('');
lines.push(`${indent}beforeEach(() => {`);
lines.push(`${indent}${indent}// Arrange: Set up test context`);
lines.push(`${indent}${indent}testContext = {`);
lines.push(`${indent}${indent}${indent}// TODO: Add test fixtures`);
lines.push(`${indent}${indent}};`);
if (this.options.generateMocks) {
lines.push('');
lines.push(`${indent}${indent}// TODO: Set up mocks`);
lines.push(`${indent}${indent}// const mockDependency = ${mockFn};`);
}
lines.push(`${indent}});`);
return lines.join('\n');
}
/**
* Generate teardown block
*/
generateTeardown(indent) {
const lines = [];
lines.push('');
lines.push(`${indent}afterEach(() => {`);
lines.push(`${indent}${indent}// TODO: Clean up after each test`);
if (this.options.framework === 'vitest') {
lines.push(`${indent}${indent}vi.clearAllMocks();`);
}
else if (this.options.framework === 'jest') {
lines.push(`${indent}${indent}jest.clearAllMocks();`);
}
lines.push(`${indent}});`);
return lines.join('\n');
}
/**
* Generate a single test case
*/
generateTestCase(testCase, indent) {
const lines = [];
const indent2 = indent + indent;
const indent3 = indent2 + indent;
// Test description comment
if (this.options.includeComments) {
lines.push(`${indent}/**`);
lines.push(`${indent} * ${testCase.id}: ${testCase.name}`);
lines.push(`${indent} * ${testCase.description}`);
lines.push(`${indent} * Type: ${testCase.type}`);
lines.push(`${indent} * Priority: ${testCase.priority}`);
lines.push(`${indent} */`);
}
// Test function
lines.push(`${indent}it('${this.escapeString(testCase.name)}', async () => {`);
// Preconditions
if (testCase.preconditions.length > 0) {
lines.push(`${indent2}// Preconditions:`);
for (const pre of testCase.preconditions) {
lines.push(`${indent2}// - ${pre}`);
}
lines.push('');
}
// Arrange
lines.push(`${indent2}// Arrange`);
if (testCase.testData.length > 0) {
lines.push(`${indent2}const testData = {`);
for (const data of testCase.testData) {
lines.push(`${indent3}${data.name}: ${this.getExampleValue(data.examples)}, // ${data.type}: ${data.constraints.join(', ')}`);
}
lines.push(`${indent2}};`);
}
else {
lines.push(`${indent2}// TODO: Set up test data`);
}
lines.push('');
// Act
lines.push(`${indent2}// Act`);
for (const step of testCase.steps) {
if (step.action.toLowerCase().includes('verify') ||
step.action.toLowerCase().includes('check')) {
continue; // Skip verification steps for Act section
}
lines.push(`${indent2}// Step ${step.number}: ${step.action}`);
lines.push(`${indent2}// TODO: Implement action`);
}
lines.push('');
// Assert
lines.push(`${indent2}// Assert`);
for (const step of testCase.steps) {
if (!step.action.toLowerCase().includes('verify') &&
!step.action.toLowerCase().includes('check')) {
continue; // Skip action steps for Assert section
}
lines.push(`${indent2}// ${step.expectedResult}`);
lines.push(`${indent2}// expect(result).toEqual(expected);`);
}
// Default assertion if no verify steps
const hasVerifySteps = testCase.steps.some(s => s.action.toLowerCase().includes('verify') ||
s.action.toLowerCase().includes('check'));
if (!hasVerifySteps) {
lines.push(`${indent2}// TODO: Add assertions`);
lines.push(`${indent2}expect(true).toBe(true); // Placeholder`);
}
// Postconditions
if (testCase.postconditions.length > 0) {
lines.push('');
lines.push(`${indent2}// Postconditions:`);
for (const post of testCase.postconditions) {
lines.push(`${indent2}// - ${post}`);
}
}
lines.push(`${indent}});`);
return lines.join('\n');
}
/**
* Generate E2E test case (Playwright format)
*/
generateE2ETestCase(testCase, indent) {
const lines = [];
const indent2 = indent + indent;
// Test description comment
if (this.options.includeComments) {
lines.push(`${indent}/**`);
lines.push(`${indent} * ${testCase.id}: ${testCase.name}`);
lines.push(`${indent} * ${testCase.description}`);
lines.push(`${indent} */`);
}
// Test function
lines.push(`${indent}test('${this.escapeString(testCase.name)}', async ({ page }) => {`);
// Steps
for (const step of testCase.steps) {
lines.push(`${indent2}// Step ${step.number}: ${step.action}`);
lines.push(this.generateE2EStepCode(step, indent2));
lines.push('');
}
// Final assertion
lines.push(`${indent2}// TODO: Add final assertions`);
lines.push(`${indent2}await expect(page).toHaveURL(/.*expected/);`);
lines.push(`${indent}});`);
return lines.join('\n');
}
/**
* Generate code for an E2E step
*/
generateE2EStepCode(step, indent) {
const action = step.action.toLowerCase();
if (action.includes('click') || action.includes('press')) {
return `${indent}await page.click('selector'); // TODO: Update selector`;
}
if (action.includes('enter') || action.includes('type') || action.includes('input')) {
return `${indent}await page.fill('selector', '${step.testData || 'test-data'}'); // TODO: Update`;
}
if (action.includes('navigate') || action.includes('go to')) {
return `${indent}await page.goto('/path'); // TODO: Update path`;
}
if (action.includes('wait')) {
return `${indent}await page.waitForSelector('selector'); // TODO: Update selector`;
}
if (action.includes('verify') || action.includes('check') || action.includes('see')) {
return `${indent}await expect(page.locator('selector')).toBeVisible(); // ${step.expectedResult}`;
}
return `${indent}// TODO: Implement step - ${step.action}`;
}
/**
* Escape string for use in test names
*/
escapeString(str) {
return str.replace(/'/g, "\\'").replace(/"/g, '\\"');
}
/**
* Get example value from test data
*/
getExampleValue(examples) {
if (examples.length === 0)
return "'test-value'";
const example = examples[0];
if (example.match(/^\d+$/))
return example;
return `'${example}'`;
}
}
//# sourceMappingURL=test-code-generator.js.map