ruch
Version:
Revolutionary React TypeScript CLI with hexagonal architecture & AI-powered development assistance. Create maintainable, scalable applications with domain-driven design and integrated AI tooling.
373 lines (307 loc) • 12.5 kB
text/typescript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import path from 'path';
import {
capitalizeFirstLetter,
createDomainInfo,
buildDomainsPath,
buildContextPath,
buildServiceContextFilePath,
buildServiceContextTestFilePath,
validateDomainStructure,
filterValidDomains,
isDirectoryEntry,
getExistingDomains,
parseExistingServiceContext,
findMissingDomains
} from '../src/utils/domain-analyzer';
import type { FileSystem } from '../src/utils/file-operations';
// Mock FileSystem pour les tests
const createMockFileSystem = (
files: Record<string, string> = {},
directories: string[] = []
): FileSystem => ({
writeFile: vi.fn() as any,
readFile: vi.fn().mockImplementation(async (filePath: string) => {
if (files[filePath]) {
return files[filePath];
}
throw new Error(`File not found: ${filePath}`);
}) as any,
readdir: vi.fn().mockImplementation(async (dirPath: string) => {
const dirEntries = Object.keys(files)
.filter(file => path.dirname(file) === dirPath)
.map(file => path.basename(file));
const subDirs = directories
.filter(dir => path.dirname(dir) === dirPath && dir !== dirPath)
.map(dir => path.basename(dir));
return [...new Set([...dirEntries, ...subDirs])];
}) as any,
stat: vi.fn().mockImplementation(async (filePath: string) => ({
isDirectory: () => directories.includes(filePath),
isFile: () => files[filePath] !== undefined
})) as any,
remove: vi.fn() as any,
mkdir: vi.fn() as any,
exists: vi.fn().mockImplementation(async (filePath: string) => {
return files[filePath] !== undefined || directories.includes(filePath);
}),
pathExists: vi.fn() as any,
ensureDir: vi.fn() as any
});
describe('Domain Analyzer', () => {
describe('Pure functions', () => {
describe('capitalizeFirstLetter', () => {
it('should capitalize first letter of lowercase string', () => {
expect(capitalizeFirstLetter('user')).toBe('User');
expect(capitalizeFirstLetter('product')).toBe('Product');
});
it('should handle already capitalized string', () => {
expect(capitalizeFirstLetter('User')).toBe('User');
});
it('should handle empty string', () => {
expect(capitalizeFirstLetter('')).toBe('');
});
it('should handle single character', () => {
expect(capitalizeFirstLetter('a')).toBe('A');
expect(capitalizeFirstLetter('A')).toBe('A');
});
});
describe('createDomainInfo', () => {
it('should create correct domain info object', () => {
const result = createDomainInfo('user');
expect(result).toEqual({
name: 'user',
capitalizedName: 'User'
});
});
it('should handle complex domain names', () => {
const result = createDomainInfo('userAccount');
expect(result).toEqual({
name: 'userAccount',
capitalizedName: 'UserAccount'
});
});
});
describe('Path builders', () => {
const projectRoot = '/test-project';
it('should build domains path correctly', () => {
const result = buildDomainsPath(projectRoot);
expect(result).toBe('/test-project/src/domains');
});
it('should build context path correctly', () => {
const result = buildContextPath(projectRoot);
expect(result).toBe('/test-project/src/context');
});
it('should build service context file path correctly', () => {
const result = buildServiceContextFilePath(projectRoot);
expect(result).toBe('/test-project/src/context/ServiceContext.tsx');
});
it('should build service context test file path correctly', () => {
const result = buildServiceContextTestFilePath(projectRoot);
expect(result).toBe('/test-project/src/context/ServiceContext.test.tsx');
});
it('should use process.cwd() as default project root', () => {
const result = buildDomainsPath();
expect(result).toBe(path.join(process.cwd(), 'src', 'domains'));
});
});
});
describe('File system operations', () => {
describe('isDirectoryEntry', () => {
it('should return true for directories', async () => {
const directories = ['/test/path'];
const fileSystem = createMockFileSystem({}, directories);
const result = await isDirectoryEntry(fileSystem, '/test/path');
expect(result).toBe(true);
});
it('should return false for files', async () => {
const files = { '/test/file.ts': 'content' };
const fileSystem = createMockFileSystem(files);
const result = await isDirectoryEntry(fileSystem, '/test/file.ts');
expect(result).toBe(false);
});
it('should return false for non-existent paths', async () => {
const fileSystem = createMockFileSystem();
const result = await isDirectoryEntry(fileSystem, '/non/existent');
expect(result).toBe(false);
});
});
describe('validateDomainStructure', () => {
it('should return true for valid domain structure', async () => {
const files = {
'/project/src/domains/user/services/UserService.ts': 'export class UserService {}',
'/project/src/domains/user/adapters/UserAdapter.ts': 'export class UserAdapter {}'
};
const fileSystem = createMockFileSystem(files);
const result = await validateDomainStructure(
fileSystem,
'/project/src/domains/user',
'user'
);
expect(result).toBe(true);
});
it('should return false for incomplete domain structure', async () => {
const files = {
'/project/src/domains/user/services/UserService.ts': 'export class UserService {}'
// Missing UserAdapter.ts
};
const fileSystem = createMockFileSystem(files);
const result = await validateDomainStructure(
fileSystem,
'/project/src/domains/user',
'user'
);
expect(result).toBe(false);
});
});
describe('filterValidDomains', () => {
it('should filter and return only valid domains', async () => {
const files = {
// Valid domain
'/project/src/domains/user/services/UserService.ts': 'content',
'/project/src/domains/user/adapters/UserAdapter.ts': 'content',
// Invalid domain (missing adapter)
'/project/src/domains/product/services/ProductService.ts': 'content',
// File instead of directory - should be filtered out
'/project/src/domains/somefile.txt': 'content'
};
const directories = [
'/project/src/domains',
'/project/src/domains/user',
'/project/src/domains/user/services',
'/project/src/domains/user/adapters',
'/project/src/domains/product',
'/project/src/domains/product/services'
];
const fileSystem = createMockFileSystem(files, directories);
const result = await filterValidDomains(
fileSystem,
'/project/src/domains',
['user', 'product', 'somefile.txt']
);
expect(result).toEqual(['user']);
});
it('should return empty array when no valid domains exist', async () => {
const directories = ['/project/src/domains'];
const fileSystem = createMockFileSystem({}, directories);
const result = await filterValidDomains(
fileSystem,
'/project/src/domains',
['invalidDomain']
);
expect(result).toEqual([]);
});
});
});
describe('Domain discovery', () => {
describe('getExistingDomains', () => {
it('should return all valid domains', async () => {
const files = {
'/project/src/domains/user/services/UserService.ts': 'content',
'/project/src/domains/user/adapters/UserAdapter.ts': 'content',
'/project/src/domains/product/services/ProductService.ts': 'content',
'/project/src/domains/product/adapters/ProductAdapter.ts': 'content'
};
const directories = [
'/project/src/domains',
'/project/src/domains/user',
'/project/src/domains/user/services',
'/project/src/domains/user/adapters',
'/project/src/domains/product',
'/project/src/domains/product/services',
'/project/src/domains/product/adapters'
];
const fileSystem = createMockFileSystem(files, directories);
const result = await getExistingDomains(fileSystem, '/project');
expect(result).toEqual([
{ name: 'user', capitalizedName: 'User' },
{ name: 'product', capitalizedName: 'Product' }
]);
});
it('should return empty array when domains directory does not exist', async () => {
const fileSystem = createMockFileSystem();
const result = await getExistingDomains(fileSystem, '/project');
expect(result).toEqual([]);
});
});
});
describe('Service context parsing', () => {
describe('parseExistingServiceContext', () => {
it('should parse domains from existing service context', async () => {
const serviceContextContent = `
interface Services {
userService: UserService;
productService: ProductService;
}
`;
const files = {
'/project/src/context/ServiceContext.tsx': serviceContextContent
};
const fileSystem = createMockFileSystem(files);
const result = await parseExistingServiceContext(fileSystem, '/project');
expect(result).toEqual([
{ name: 'user', capitalizedName: 'User' },
{ name: 'product', capitalizedName: 'Product' }
]);
});
it('should return empty array when service context does not exist', async () => {
const fileSystem = createMockFileSystem();
const result = await parseExistingServiceContext(fileSystem, '/project');
expect(result).toEqual([]);
});
});
});
describe('Missing domains detection', () => {
describe('findMissingDomains', () => {
it('should find domains missing from service context', async () => {
// Existing domains in filesystem
const files = {
'/project/src/domains/user/services/UserService.ts': 'content',
'/project/src/domains/user/adapters/UserAdapter.ts': 'content',
'/project/src/domains/order/services/OrderService.ts': 'content',
'/project/src/domains/order/adapters/OrderAdapter.ts': 'content',
// Service context only includes user
'/project/src/context/ServiceContext.tsx': `
interface Services {
userService: UserService;
}
`
};
const directories = [
'/project/src/domains',
'/project/src/domains/user',
'/project/src/domains/user/services',
'/project/src/domains/user/adapters',
'/project/src/domains/order',
'/project/src/domains/order/services',
'/project/src/domains/order/adapters'
];
const fileSystem = createMockFileSystem(files, directories);
const result = await findMissingDomains(fileSystem, '/project');
expect(result).toEqual([
{ name: 'order', capitalizedName: 'Order' }
]);
});
it('should return empty array when all domains are in service context', async () => {
const files = {
'/project/src/domains/user/services/UserService.ts': 'content',
'/project/src/domains/user/adapters/UserAdapter.ts': 'content',
'/project/src/context/ServiceContext.tsx': `
interface Services {
userService: UserService;
}
`
};
const directories = [
'/project/src/domains',
'/project/src/domains/user',
'/project/src/domains/user/services',
'/project/src/domains/user/adapters'
];
const fileSystem = createMockFileSystem(files, directories);
const result = await findMissingDomains(fileSystem, '/project');
expect(result).toEqual([]);
});
});
});
});