@codebakers/cli 3.9.15 โ†’ 3.9.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -798,6 +798,28 @@ class CodeBakersServer {
798
798
  },
799
799
  },
800
800
  },
801
+ {
802
+ name: 'generate_tests',
803
+ description: 'Generate test stubs for a file or feature. Creates a test file with happy path and error case templates based on the source code. Reduces friction for adding tests. Use when user needs help writing tests.',
804
+ inputSchema: {
805
+ type: 'object',
806
+ properties: {
807
+ file: {
808
+ type: 'string',
809
+ description: 'Source file to generate tests for (e.g., "src/components/LoginForm.tsx", "src/app/api/users/route.ts")',
810
+ },
811
+ feature: {
812
+ type: 'string',
813
+ description: 'Feature name if generating tests for a feature rather than a specific file',
814
+ },
815
+ testType: {
816
+ type: 'string',
817
+ enum: ['unit', 'integration', 'e2e'],
818
+ description: 'Type of test to generate (default: unit for components/functions, integration for API routes)',
819
+ },
820
+ },
821
+ },
822
+ },
801
823
  {
802
824
  name: 'validate_complete',
803
825
  description: 'MANDATORY: Call this BEFORE saying "done" or "complete" on any feature. Validates that tests exist, tests pass, and TypeScript compiles. Returns { valid: true } or { valid: false, missing: [...] }. You are NOT ALLOWED to complete a feature without calling this first.',
@@ -1516,6 +1538,8 @@ class CodeBakersServer {
1516
1538
  return this.handleProjectStatus();
1517
1539
  case 'run_tests':
1518
1540
  return this.handleRunTests(args);
1541
+ case 'generate_tests':
1542
+ return this.handleGenerateTests(args);
1519
1543
  case 'validate_complete':
1520
1544
  return this.handleValidateComplete(args);
1521
1545
  case 'discover_patterns':
@@ -3629,6 +3653,270 @@ Just describe what you want to build! I'll automatically:
3629
3653
  }],
3630
3654
  };
3631
3655
  }
3656
+ /**
3657
+ * Generate test stubs for a file or feature
3658
+ * Analyzes source code and creates appropriate test templates
3659
+ */
3660
+ handleGenerateTests(args) {
3661
+ const { file, feature, testType } = args;
3662
+ const cwd = process.cwd();
3663
+ if (!file && !feature) {
3664
+ return {
3665
+ content: [{
3666
+ type: 'text',
3667
+ text: 'โŒ Please provide either a file path or feature name to generate tests for.',
3668
+ }],
3669
+ isError: true,
3670
+ };
3671
+ }
3672
+ // Detect test framework
3673
+ let testFramework = 'vitest';
3674
+ try {
3675
+ const pkgPath = path.join(cwd, 'package.json');
3676
+ if (fs.existsSync(pkgPath)) {
3677
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
3678
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3679
+ if (deps['jest'])
3680
+ testFramework = 'jest';
3681
+ else if (deps['@playwright/test'])
3682
+ testFramework = 'playwright';
3683
+ else if (deps['vitest'])
3684
+ testFramework = 'vitest';
3685
+ }
3686
+ }
3687
+ catch {
3688
+ // Use default
3689
+ }
3690
+ let response = `# ๐Ÿงช Test Stub Generator\n\n`;
3691
+ if (file) {
3692
+ // Generate tests for a specific file
3693
+ const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
3694
+ if (!fs.existsSync(filePath)) {
3695
+ return {
3696
+ content: [{
3697
+ type: 'text',
3698
+ text: `โŒ File not found: ${file}`,
3699
+ }],
3700
+ isError: true,
3701
+ };
3702
+ }
3703
+ const content = fs.readFileSync(filePath, 'utf-8');
3704
+ const fileName = path.basename(file);
3705
+ const fileExt = path.extname(file);
3706
+ const isApiRoute = file.includes('/api/') || file.includes('\\api\\');
3707
+ const isComponent = fileExt === '.tsx' && !isApiRoute;
3708
+ const isService = file.includes('/services/') || file.includes('\\services\\') || file.includes('/lib/') || file.includes('\\lib\\');
3709
+ // Detect exported functions/components
3710
+ const exportedItems = [];
3711
+ const exportDefaultMatch = content.match(/export\s+default\s+(function\s+)?(\w+)/);
3712
+ const exportMatches = content.matchAll(/export\s+(async\s+)?(function|const)\s+(\w+)/g);
3713
+ if (exportDefaultMatch && exportDefaultMatch[2]) {
3714
+ exportedItems.push(exportDefaultMatch[2]);
3715
+ }
3716
+ for (const match of exportMatches) {
3717
+ if (match[3])
3718
+ exportedItems.push(match[3]);
3719
+ }
3720
+ // HTTP methods for API routes
3721
+ const httpMethods = [];
3722
+ if (isApiRoute) {
3723
+ if (content.includes('export async function GET') || content.includes('export function GET'))
3724
+ httpMethods.push('GET');
3725
+ if (content.includes('export async function POST') || content.includes('export function POST'))
3726
+ httpMethods.push('POST');
3727
+ if (content.includes('export async function PUT') || content.includes('export function PUT'))
3728
+ httpMethods.push('PUT');
3729
+ if (content.includes('export async function PATCH') || content.includes('export function PATCH'))
3730
+ httpMethods.push('PATCH');
3731
+ if (content.includes('export async function DELETE') || content.includes('export function DELETE'))
3732
+ httpMethods.push('DELETE');
3733
+ }
3734
+ response += `**Source:** \`${file}\`\n`;
3735
+ response += `**Type:** ${isApiRoute ? 'API Route' : isComponent ? 'React Component' : isService ? 'Service/Utility' : 'Module'}\n`;
3736
+ response += `**Test Framework:** ${testFramework}\n\n`;
3737
+ // Determine test file path
3738
+ const testFileName = fileName.replace(/\.(ts|tsx)$/, '.test$1');
3739
+ let testFilePath;
3740
+ if (isApiRoute) {
3741
+ // API routes: tests/api/[route].test.ts
3742
+ const routePath = file.replace(/.*\/api\//, '').replace(/\/route\.(ts|tsx)$/, '').replace(/\\/g, '/');
3743
+ testFilePath = `tests/api/${routePath}.test.ts`;
3744
+ }
3745
+ else if (isComponent) {
3746
+ // Components: alongside the file
3747
+ testFilePath = file.replace(/\.(tsx)$/, '.test.tsx');
3748
+ }
3749
+ else {
3750
+ // Services/utils: tests/services/
3751
+ testFilePath = `tests/${fileName.replace(/\.(ts|tsx)$/, '.test.ts')}`;
3752
+ }
3753
+ response += `**Test File:** \`${testFilePath}\`\n\n`;
3754
+ response += `---\n\n`;
3755
+ // Generate test stub based on file type
3756
+ if (isApiRoute && httpMethods.length > 0) {
3757
+ response += `## API Route Test Stub\n\n`;
3758
+ response += '```typescript\n';
3759
+ response += `import { describe, it, expect, beforeEach } from '${testFramework}';\n\n`;
3760
+ response += `describe('${file.replace(/.*\/api\//, '/api/').replace(/\/route\.(ts|tsx)$/, '')}', () => {\n`;
3761
+ for (const method of httpMethods) {
3762
+ response += ` describe('${method}', () => {\n`;
3763
+ response += ` it('should handle successful request', async () => {\n`;
3764
+ response += ` // Arrange: Set up test data\n`;
3765
+ response += ` const request = new Request('http://localhost/api/...', {\n`;
3766
+ response += ` method: '${method}',\n`;
3767
+ if (method !== 'GET' && method !== 'DELETE') {
3768
+ response += ` body: JSON.stringify({ /* test data */ }),\n`;
3769
+ response += ` headers: { 'Content-Type': 'application/json' },\n`;
3770
+ }
3771
+ response += ` });\n\n`;
3772
+ response += ` // Act: Call the handler\n`;
3773
+ response += ` // const response = await ${method}(request);\n`;
3774
+ response += ` // const data = await response.json();\n\n`;
3775
+ response += ` // Assert: Check response\n`;
3776
+ response += ` // expect(response.status).toBe(200);\n`;
3777
+ response += ` // expect(data).toMatchObject({ /* expected */ });\n`;
3778
+ response += ` });\n\n`;
3779
+ response += ` it('should handle validation errors', async () => {\n`;
3780
+ response += ` // Test with invalid input\n`;
3781
+ response += ` // expect(response.status).toBe(400);\n`;
3782
+ response += ` });\n\n`;
3783
+ response += ` it('should handle unauthorized access', async () => {\n`;
3784
+ response += ` // Test without auth\n`;
3785
+ response += ` // expect(response.status).toBe(401);\n`;
3786
+ response += ` });\n`;
3787
+ response += ` });\n\n`;
3788
+ }
3789
+ response += `});\n`;
3790
+ response += '```\n\n';
3791
+ }
3792
+ else if (isComponent) {
3793
+ const componentName = exportedItems[0] || fileName.replace(/\.(tsx)$/, '');
3794
+ response += `## Component Test Stub\n\n`;
3795
+ response += '```typescript\n';
3796
+ response += `import { render, screen, fireEvent } from '@testing-library/react';\n`;
3797
+ response += `import { describe, it, expect } from '${testFramework}';\n`;
3798
+ response += `import { ${componentName} } from './${fileName.replace(/\.tsx$/, '')}';\n\n`;
3799
+ response += `describe('${componentName}', () => {\n`;
3800
+ response += ` it('renders correctly', () => {\n`;
3801
+ response += ` render(<${componentName} />);\n`;
3802
+ response += ` // expect(screen.getByRole('...')).toBeInTheDocument();\n`;
3803
+ response += ` });\n\n`;
3804
+ response += ` it('handles user interaction', async () => {\n`;
3805
+ response += ` render(<${componentName} />);\n`;
3806
+ response += ` // const button = screen.getByRole('button', { name: /.../ });\n`;
3807
+ response += ` // await fireEvent.click(button);\n`;
3808
+ response += ` // expect(screen.getByText('...')).toBeInTheDocument();\n`;
3809
+ response += ` });\n\n`;
3810
+ response += ` it('displays loading state', () => {\n`;
3811
+ response += ` // Test loading state\n`;
3812
+ response += ` });\n\n`;
3813
+ response += ` it('handles errors gracefully', () => {\n`;
3814
+ response += ` // Test error state\n`;
3815
+ response += ` });\n`;
3816
+ response += `});\n`;
3817
+ response += '```\n\n';
3818
+ }
3819
+ else {
3820
+ // Generic function/service tests
3821
+ response += `## Unit Test Stub\n\n`;
3822
+ response += '```typescript\n';
3823
+ response += `import { describe, it, expect, vi } from '${testFramework}';\n`;
3824
+ if (exportedItems.length > 0) {
3825
+ response += `import { ${exportedItems.join(', ')} } from '${file.replace(/\.(ts|tsx)$/, '')}';\n`;
3826
+ }
3827
+ response += `\n`;
3828
+ response += `describe('${fileName.replace(/\.(ts|tsx)$/, '')}', () => {\n`;
3829
+ for (const item of exportedItems.slice(0, 5)) { // Limit to first 5
3830
+ response += ` describe('${item}', () => {\n`;
3831
+ response += ` it('should work correctly with valid input', async () => {\n`;
3832
+ response += ` // Arrange\n`;
3833
+ response += ` const input = { /* test data */ };\n\n`;
3834
+ response += ` // Act\n`;
3835
+ response += ` // const result = await ${item}(input);\n\n`;
3836
+ response += ` // Assert\n`;
3837
+ response += ` // expect(result).toBe(/* expected */);\n`;
3838
+ response += ` });\n\n`;
3839
+ response += ` it('should handle edge cases', async () => {\n`;
3840
+ response += ` // Test with empty/null/undefined inputs\n`;
3841
+ response += ` });\n\n`;
3842
+ response += ` it('should throw on invalid input', async () => {\n`;
3843
+ response += ` // expect(() => ${item}(invalid)).toThrow();\n`;
3844
+ response += ` });\n`;
3845
+ response += ` });\n\n`;
3846
+ }
3847
+ response += `});\n`;
3848
+ response += '```\n\n';
3849
+ }
3850
+ }
3851
+ else if (feature) {
3852
+ // Generate feature-based test structure
3853
+ response += `**Feature:** ${feature}\n`;
3854
+ response += `**Test Framework:** ${testFramework}\n\n`;
3855
+ response += `---\n\n`;
3856
+ response += `## Feature Test Structure\n\n`;
3857
+ response += `For feature "${feature}", create the following test files:\n\n`;
3858
+ response += `### 1. Unit Tests (\`tests/unit/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
3859
+ response += '```typescript\n';
3860
+ response += `import { describe, it, expect } from '${testFramework}';\n\n`;
3861
+ response += `describe('${feature} - Unit Tests', () => {\n`;
3862
+ response += ` describe('Core Logic', () => {\n`;
3863
+ response += ` it('should handle happy path', () => {\n`;
3864
+ response += ` // Test the main success scenario\n`;
3865
+ response += ` });\n\n`;
3866
+ response += ` it('should validate input', () => {\n`;
3867
+ response += ` // Test input validation\n`;
3868
+ response += ` });\n\n`;
3869
+ response += ` it('should handle errors', () => {\n`;
3870
+ response += ` // Test error handling\n`;
3871
+ response += ` });\n`;
3872
+ response += ` });\n`;
3873
+ response += `});\n`;
3874
+ response += '```\n\n';
3875
+ response += `### 2. Integration Tests (\`tests/integration/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
3876
+ response += '```typescript\n';
3877
+ response += `import { describe, it, expect, beforeAll, afterAll } from '${testFramework}';\n\n`;
3878
+ response += `describe('${feature} - Integration Tests', () => {\n`;
3879
+ response += ` beforeAll(async () => {\n`;
3880
+ response += ` // Set up test database, mock services, etc.\n`;
3881
+ response += ` });\n\n`;
3882
+ response += ` afterAll(async () => {\n`;
3883
+ response += ` // Clean up\n`;
3884
+ response += ` });\n\n`;
3885
+ response += ` it('should complete the full flow', async () => {\n`;
3886
+ response += ` // Test the complete feature flow\n`;
3887
+ response += ` });\n`;
3888
+ response += `});\n`;
3889
+ response += '```\n\n';
3890
+ if (testType === 'e2e' || testFramework === 'playwright') {
3891
+ response += `### 3. E2E Tests (\`e2e/${feature.toLowerCase().replace(/\s+/g, '-')}.spec.ts\`)\n\n`;
3892
+ response += '```typescript\n';
3893
+ response += `import { test, expect } from '@playwright/test';\n\n`;
3894
+ response += `test.describe('${feature}', () => {\n`;
3895
+ response += ` test('user can complete the flow', async ({ page }) => {\n`;
3896
+ response += ` // Navigate to the feature\n`;
3897
+ response += ` await page.goto('/...');\n\n`;
3898
+ response += ` // Interact with the UI\n`;
3899
+ response += ` // await page.click('button');\n`;
3900
+ response += ` // await page.fill('input', 'value');\n\n`;
3901
+ response += ` // Verify the result\n`;
3902
+ response += ` // await expect(page.getByText('...')).toBeVisible();\n`;
3903
+ response += ` });\n`;
3904
+ response += `});\n`;
3905
+ response += '```\n\n';
3906
+ }
3907
+ }
3908
+ response += `---\n\n`;
3909
+ response += `**Next Steps:**\n`;
3910
+ response += `1. Create the test file at the suggested path\n`;
3911
+ response += `2. Uncomment and fill in the test implementations\n`;
3912
+ response += `3. Run tests: \`npm test\`\n`;
3913
+ return {
3914
+ content: [{
3915
+ type: 'text',
3916
+ text: response,
3917
+ }],
3918
+ };
3919
+ }
3632
3920
  /**
3633
3921
  * MANDATORY: Validate that a feature is complete before AI can say "done" (v6.0 Server-Side)
3634
3922
  * Runs local checks (tests, TypeScript), then validates with server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "3.9.15",
3
+ "version": "3.9.16",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/mcp/server.ts CHANGED
@@ -878,6 +878,29 @@ class CodeBakersServer {
878
878
  },
879
879
  },
880
880
  },
881
+ {
882
+ name: 'generate_tests',
883
+ description:
884
+ 'Generate test stubs for a file or feature. Creates a test file with happy path and error case templates based on the source code. Reduces friction for adding tests. Use when user needs help writing tests.',
885
+ inputSchema: {
886
+ type: 'object' as const,
887
+ properties: {
888
+ file: {
889
+ type: 'string',
890
+ description: 'Source file to generate tests for (e.g., "src/components/LoginForm.tsx", "src/app/api/users/route.ts")',
891
+ },
892
+ feature: {
893
+ type: 'string',
894
+ description: 'Feature name if generating tests for a feature rather than a specific file',
895
+ },
896
+ testType: {
897
+ type: 'string',
898
+ enum: ['unit', 'integration', 'e2e'],
899
+ description: 'Type of test to generate (default: unit for components/functions, integration for API routes)',
900
+ },
901
+ },
902
+ },
903
+ },
881
904
  {
882
905
  name: 'validate_complete',
883
906
  description:
@@ -1659,6 +1682,9 @@ class CodeBakersServer {
1659
1682
  case 'run_tests':
1660
1683
  return this.handleRunTests(args as { filter?: string; watch?: boolean });
1661
1684
 
1685
+ case 'generate_tests':
1686
+ return this.handleGenerateTests(args as { file?: string; feature?: string; testType?: 'unit' | 'integration' | 'e2e' });
1687
+
1662
1688
  case 'validate_complete':
1663
1689
  return this.handleValidateComplete(args as { feature: string; files?: string[] });
1664
1690
 
@@ -4079,6 +4105,281 @@ Just describe what you want to build! I'll automatically:
4079
4105
  };
4080
4106
  }
4081
4107
 
4108
+ /**
4109
+ * Generate test stubs for a file or feature
4110
+ * Analyzes source code and creates appropriate test templates
4111
+ */
4112
+ private handleGenerateTests(args: { file?: string; feature?: string; testType?: 'unit' | 'integration' | 'e2e' }) {
4113
+ const { file, feature, testType } = args;
4114
+ const cwd = process.cwd();
4115
+
4116
+ if (!file && !feature) {
4117
+ return {
4118
+ content: [{
4119
+ type: 'text' as const,
4120
+ text: 'โŒ Please provide either a file path or feature name to generate tests for.',
4121
+ }],
4122
+ isError: true,
4123
+ };
4124
+ }
4125
+
4126
+ // Detect test framework
4127
+ let testFramework = 'vitest';
4128
+ try {
4129
+ const pkgPath = path.join(cwd, 'package.json');
4130
+ if (fs.existsSync(pkgPath)) {
4131
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
4132
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
4133
+ if (deps['jest']) testFramework = 'jest';
4134
+ else if (deps['@playwright/test']) testFramework = 'playwright';
4135
+ else if (deps['vitest']) testFramework = 'vitest';
4136
+ }
4137
+ } catch {
4138
+ // Use default
4139
+ }
4140
+
4141
+ let response = `# ๐Ÿงช Test Stub Generator\n\n`;
4142
+
4143
+ if (file) {
4144
+ // Generate tests for a specific file
4145
+ const filePath = path.isAbsolute(file) ? file : path.join(cwd, file);
4146
+
4147
+ if (!fs.existsSync(filePath)) {
4148
+ return {
4149
+ content: [{
4150
+ type: 'text' as const,
4151
+ text: `โŒ File not found: ${file}`,
4152
+ }],
4153
+ isError: true,
4154
+ };
4155
+ }
4156
+
4157
+ const content = fs.readFileSync(filePath, 'utf-8');
4158
+ const fileName = path.basename(file);
4159
+ const fileExt = path.extname(file);
4160
+ const isApiRoute = file.includes('/api/') || file.includes('\\api\\');
4161
+ const isComponent = fileExt === '.tsx' && !isApiRoute;
4162
+ const isService = file.includes('/services/') || file.includes('\\services\\') || file.includes('/lib/') || file.includes('\\lib\\');
4163
+
4164
+ // Detect exported functions/components
4165
+ const exportedItems: string[] = [];
4166
+ const exportDefaultMatch = content.match(/export\s+default\s+(function\s+)?(\w+)/);
4167
+ const exportMatches = content.matchAll(/export\s+(async\s+)?(function|const)\s+(\w+)/g);
4168
+
4169
+ if (exportDefaultMatch && exportDefaultMatch[2]) {
4170
+ exportedItems.push(exportDefaultMatch[2]);
4171
+ }
4172
+ for (const match of exportMatches) {
4173
+ if (match[3]) exportedItems.push(match[3]);
4174
+ }
4175
+
4176
+ // HTTP methods for API routes
4177
+ const httpMethods: string[] = [];
4178
+ if (isApiRoute) {
4179
+ if (content.includes('export async function GET') || content.includes('export function GET')) httpMethods.push('GET');
4180
+ if (content.includes('export async function POST') || content.includes('export function POST')) httpMethods.push('POST');
4181
+ if (content.includes('export async function PUT') || content.includes('export function PUT')) httpMethods.push('PUT');
4182
+ if (content.includes('export async function PATCH') || content.includes('export function PATCH')) httpMethods.push('PATCH');
4183
+ if (content.includes('export async function DELETE') || content.includes('export function DELETE')) httpMethods.push('DELETE');
4184
+ }
4185
+
4186
+ response += `**Source:** \`${file}\`\n`;
4187
+ response += `**Type:** ${isApiRoute ? 'API Route' : isComponent ? 'React Component' : isService ? 'Service/Utility' : 'Module'}\n`;
4188
+ response += `**Test Framework:** ${testFramework}\n\n`;
4189
+
4190
+ // Determine test file path
4191
+ const testFileName = fileName.replace(/\.(ts|tsx)$/, '.test$1');
4192
+ let testFilePath: string;
4193
+
4194
+ if (isApiRoute) {
4195
+ // API routes: tests/api/[route].test.ts
4196
+ const routePath = file.replace(/.*\/api\//, '').replace(/\/route\.(ts|tsx)$/, '').replace(/\\/g, '/');
4197
+ testFilePath = `tests/api/${routePath}.test.ts`;
4198
+ } else if (isComponent) {
4199
+ // Components: alongside the file
4200
+ testFilePath = file.replace(/\.(tsx)$/, '.test.tsx');
4201
+ } else {
4202
+ // Services/utils: tests/services/
4203
+ testFilePath = `tests/${fileName.replace(/\.(ts|tsx)$/, '.test.ts')}`;
4204
+ }
4205
+
4206
+ response += `**Test File:** \`${testFilePath}\`\n\n`;
4207
+ response += `---\n\n`;
4208
+
4209
+ // Generate test stub based on file type
4210
+ if (isApiRoute && httpMethods.length > 0) {
4211
+ response += `## API Route Test Stub\n\n`;
4212
+ response += '```typescript\n';
4213
+ response += `import { describe, it, expect, beforeEach } from '${testFramework}';\n\n`;
4214
+ response += `describe('${file.replace(/.*\/api\//, '/api/').replace(/\/route\.(ts|tsx)$/, '')}', () => {\n`;
4215
+
4216
+ for (const method of httpMethods) {
4217
+ response += ` describe('${method}', () => {\n`;
4218
+ response += ` it('should handle successful request', async () => {\n`;
4219
+ response += ` // Arrange: Set up test data\n`;
4220
+ response += ` const request = new Request('http://localhost/api/...', {\n`;
4221
+ response += ` method: '${method}',\n`;
4222
+ if (method !== 'GET' && method !== 'DELETE') {
4223
+ response += ` body: JSON.stringify({ /* test data */ }),\n`;
4224
+ response += ` headers: { 'Content-Type': 'application/json' },\n`;
4225
+ }
4226
+ response += ` });\n\n`;
4227
+ response += ` // Act: Call the handler\n`;
4228
+ response += ` // const response = await ${method}(request);\n`;
4229
+ response += ` // const data = await response.json();\n\n`;
4230
+ response += ` // Assert: Check response\n`;
4231
+ response += ` // expect(response.status).toBe(200);\n`;
4232
+ response += ` // expect(data).toMatchObject({ /* expected */ });\n`;
4233
+ response += ` });\n\n`;
4234
+ response += ` it('should handle validation errors', async () => {\n`;
4235
+ response += ` // Test with invalid input\n`;
4236
+ response += ` // expect(response.status).toBe(400);\n`;
4237
+ response += ` });\n\n`;
4238
+ response += ` it('should handle unauthorized access', async () => {\n`;
4239
+ response += ` // Test without auth\n`;
4240
+ response += ` // expect(response.status).toBe(401);\n`;
4241
+ response += ` });\n`;
4242
+ response += ` });\n\n`;
4243
+ }
4244
+ response += `});\n`;
4245
+ response += '```\n\n';
4246
+
4247
+ } else if (isComponent) {
4248
+ const componentName = exportedItems[0] || fileName.replace(/\.(tsx)$/, '');
4249
+ response += `## Component Test Stub\n\n`;
4250
+ response += '```typescript\n';
4251
+ response += `import { render, screen, fireEvent } from '@testing-library/react';\n`;
4252
+ response += `import { describe, it, expect } from '${testFramework}';\n`;
4253
+ response += `import { ${componentName} } from './${fileName.replace(/\.tsx$/, '')}';\n\n`;
4254
+ response += `describe('${componentName}', () => {\n`;
4255
+ response += ` it('renders correctly', () => {\n`;
4256
+ response += ` render(<${componentName} />);\n`;
4257
+ response += ` // expect(screen.getByRole('...')).toBeInTheDocument();\n`;
4258
+ response += ` });\n\n`;
4259
+ response += ` it('handles user interaction', async () => {\n`;
4260
+ response += ` render(<${componentName} />);\n`;
4261
+ response += ` // const button = screen.getByRole('button', { name: /.../ });\n`;
4262
+ response += ` // await fireEvent.click(button);\n`;
4263
+ response += ` // expect(screen.getByText('...')).toBeInTheDocument();\n`;
4264
+ response += ` });\n\n`;
4265
+ response += ` it('displays loading state', () => {\n`;
4266
+ response += ` // Test loading state\n`;
4267
+ response += ` });\n\n`;
4268
+ response += ` it('handles errors gracefully', () => {\n`;
4269
+ response += ` // Test error state\n`;
4270
+ response += ` });\n`;
4271
+ response += `});\n`;
4272
+ response += '```\n\n';
4273
+
4274
+ } else {
4275
+ // Generic function/service tests
4276
+ response += `## Unit Test Stub\n\n`;
4277
+ response += '```typescript\n';
4278
+ response += `import { describe, it, expect, vi } from '${testFramework}';\n`;
4279
+ if (exportedItems.length > 0) {
4280
+ response += `import { ${exportedItems.join(', ')} } from '${file.replace(/\.(ts|tsx)$/, '')}';\n`;
4281
+ }
4282
+ response += `\n`;
4283
+ response += `describe('${fileName.replace(/\.(ts|tsx)$/, '')}', () => {\n`;
4284
+
4285
+ for (const item of exportedItems.slice(0, 5)) { // Limit to first 5
4286
+ response += ` describe('${item}', () => {\n`;
4287
+ response += ` it('should work correctly with valid input', async () => {\n`;
4288
+ response += ` // Arrange\n`;
4289
+ response += ` const input = { /* test data */ };\n\n`;
4290
+ response += ` // Act\n`;
4291
+ response += ` // const result = await ${item}(input);\n\n`;
4292
+ response += ` // Assert\n`;
4293
+ response += ` // expect(result).toBe(/* expected */);\n`;
4294
+ response += ` });\n\n`;
4295
+ response += ` it('should handle edge cases', async () => {\n`;
4296
+ response += ` // Test with empty/null/undefined inputs\n`;
4297
+ response += ` });\n\n`;
4298
+ response += ` it('should throw on invalid input', async () => {\n`;
4299
+ response += ` // expect(() => ${item}(invalid)).toThrow();\n`;
4300
+ response += ` });\n`;
4301
+ response += ` });\n\n`;
4302
+ }
4303
+ response += `});\n`;
4304
+ response += '```\n\n';
4305
+ }
4306
+
4307
+ } else if (feature) {
4308
+ // Generate feature-based test structure
4309
+ response += `**Feature:** ${feature}\n`;
4310
+ response += `**Test Framework:** ${testFramework}\n\n`;
4311
+ response += `---\n\n`;
4312
+
4313
+ response += `## Feature Test Structure\n\n`;
4314
+ response += `For feature "${feature}", create the following test files:\n\n`;
4315
+
4316
+ response += `### 1. Unit Tests (\`tests/unit/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
4317
+ response += '```typescript\n';
4318
+ response += `import { describe, it, expect } from '${testFramework}';\n\n`;
4319
+ response += `describe('${feature} - Unit Tests', () => {\n`;
4320
+ response += ` describe('Core Logic', () => {\n`;
4321
+ response += ` it('should handle happy path', () => {\n`;
4322
+ response += ` // Test the main success scenario\n`;
4323
+ response += ` });\n\n`;
4324
+ response += ` it('should validate input', () => {\n`;
4325
+ response += ` // Test input validation\n`;
4326
+ response += ` });\n\n`;
4327
+ response += ` it('should handle errors', () => {\n`;
4328
+ response += ` // Test error handling\n`;
4329
+ response += ` });\n`;
4330
+ response += ` });\n`;
4331
+ response += `});\n`;
4332
+ response += '```\n\n';
4333
+
4334
+ response += `### 2. Integration Tests (\`tests/integration/${feature.toLowerCase().replace(/\s+/g, '-')}.test.ts\`)\n\n`;
4335
+ response += '```typescript\n';
4336
+ response += `import { describe, it, expect, beforeAll, afterAll } from '${testFramework}';\n\n`;
4337
+ response += `describe('${feature} - Integration Tests', () => {\n`;
4338
+ response += ` beforeAll(async () => {\n`;
4339
+ response += ` // Set up test database, mock services, etc.\n`;
4340
+ response += ` });\n\n`;
4341
+ response += ` afterAll(async () => {\n`;
4342
+ response += ` // Clean up\n`;
4343
+ response += ` });\n\n`;
4344
+ response += ` it('should complete the full flow', async () => {\n`;
4345
+ response += ` // Test the complete feature flow\n`;
4346
+ response += ` });\n`;
4347
+ response += `});\n`;
4348
+ response += '```\n\n';
4349
+
4350
+ if (testType === 'e2e' || testFramework === 'playwright') {
4351
+ response += `### 3. E2E Tests (\`e2e/${feature.toLowerCase().replace(/\s+/g, '-')}.spec.ts\`)\n\n`;
4352
+ response += '```typescript\n';
4353
+ response += `import { test, expect } from '@playwright/test';\n\n`;
4354
+ response += `test.describe('${feature}', () => {\n`;
4355
+ response += ` test('user can complete the flow', async ({ page }) => {\n`;
4356
+ response += ` // Navigate to the feature\n`;
4357
+ response += ` await page.goto('/...');\n\n`;
4358
+ response += ` // Interact with the UI\n`;
4359
+ response += ` // await page.click('button');\n`;
4360
+ response += ` // await page.fill('input', 'value');\n\n`;
4361
+ response += ` // Verify the result\n`;
4362
+ response += ` // await expect(page.getByText('...')).toBeVisible();\n`;
4363
+ response += ` });\n`;
4364
+ response += `});\n`;
4365
+ response += '```\n\n';
4366
+ }
4367
+ }
4368
+
4369
+ response += `---\n\n`;
4370
+ response += `**Next Steps:**\n`;
4371
+ response += `1. Create the test file at the suggested path\n`;
4372
+ response += `2. Uncomment and fill in the test implementations\n`;
4373
+ response += `3. Run tests: \`npm test\`\n`;
4374
+
4375
+ return {
4376
+ content: [{
4377
+ type: 'text' as const,
4378
+ text: response,
4379
+ }],
4380
+ };
4381
+ }
4382
+
4082
4383
  /**
4083
4384
  * MANDATORY: Validate that a feature is complete before AI can say "done" (v6.0 Server-Side)
4084
4385
  * Runs local checks (tests, TypeScript), then validates with server