Skip to content
This repository was archived by the owner on Dec 17, 2025. It is now read-only.

Commit 5617e0d

Browse files
committed
fix: new tests
1 parent e2234ac commit 5617e0d

File tree

6 files changed

+402
-50
lines changed

6 files changed

+402
-50
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout
18-
uses: actions/checkout@v3
18+
uses: actions/checkout@v4
1919
with:
2020
fetch-depth: 0
2121

2222
- name: Setup Node.js
23-
uses: actions/setup-node@v3
23+
uses: actions/setup-node@v4
2424
with:
25-
node-version: "lts/*"
25+
node-version: "18.x"
2626

2727
- name: Install dependencies
2828
run: npm ci

test/.context/diagrams/test.mmd

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/cli.test.ts

Lines changed: 0 additions & 44 deletions
This file was deleted.

test/lib/ContextGenerator.test.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { jest } from '@jest/globals';
2+
import { ContextGenerator } from '../../src/lib/ContextGenerator.js';
3+
import * as path from 'node:path';
4+
5+
// Mock fs/promises module
6+
jest.mock('node:fs/promises', () => ({
7+
access: jest.fn(),
8+
mkdir: jest.fn(),
9+
writeFile: jest.fn()
10+
}));
11+
12+
import * as fs from 'node:fs/promises';
13+
14+
describe('ContextGenerator', () => {
15+
let generator: ContextGenerator;
16+
const mockFs = fs as jest.Mocked<typeof fs>;
17+
18+
beforeEach(() => {
19+
generator = new ContextGenerator();
20+
jest.clearAllMocks();
21+
// Default mocks for non-existing files
22+
mockFs.access.mockRejectedValue(new Error('ENOENT'));
23+
mockFs.mkdir.mockResolvedValue(undefined);
24+
mockFs.writeFile.mockResolvedValue(undefined);
25+
});
26+
27+
describe('generateContextDir', () => {
28+
it('should create directory when it does not exist', async () => {
29+
const result = await generator.generateContextDir();
30+
expect(result).toBe(true);
31+
expect(mockFs.mkdir).toHaveBeenCalledWith('.context', { recursive: true });
32+
});
33+
34+
it('should not create directory when it already exists', async () => {
35+
mockFs.access.mockResolvedValue(undefined);
36+
const result = await generator.generateContextDir();
37+
expect(result).toBe(false);
38+
expect(mockFs.mkdir).not.toHaveBeenCalled();
39+
});
40+
41+
it('should throw error when directory creation fails', async () => {
42+
mockFs.mkdir.mockRejectedValue(new Error('Permission denied'));
43+
await expect(generator.generateContextDir()).rejects.toThrow('Failed to generate context directory');
44+
});
45+
});
46+
47+
describe('generateIndexFile', () => {
48+
it('should create index.md when it does not exist', async () => {
49+
const result = await generator.generateIndexFile();
50+
expect(result).toBe(true);
51+
expect(mockFs.writeFile).toHaveBeenCalledWith(
52+
expect.stringContaining('index.md'),
53+
expect.stringContaining('module-name:')
54+
);
55+
});
56+
57+
it('should not create index.md when it already exists', async () => {
58+
mockFs.access.mockResolvedValue(undefined);
59+
const result = await generator.generateIndexFile();
60+
expect(result).toBe(false);
61+
expect(mockFs.writeFile).not.toHaveBeenCalled();
62+
});
63+
64+
it('should throw error when file creation fails', async () => {
65+
mockFs.writeFile.mockRejectedValue(new Error('Permission denied'));
66+
await expect(generator.generateIndexFile()).rejects.toThrow('Failed to generate index file');
67+
});
68+
69+
it('should include all required sections in index.md content', async () => {
70+
await generator.generateIndexFile();
71+
const writeFileCall = mockFs.writeFile.mock.calls[0];
72+
const content = writeFileCall[1] as string;
73+
74+
expect(content).toContain('module-name:');
75+
expect(content).toContain('version:');
76+
expect(content).toContain('description:');
77+
expect(content).toContain('technologies:');
78+
expect(content).toContain('architecture:');
79+
expect(content).toContain('development:');
80+
expect(content).toContain('business-requirements:');
81+
expect(content).toContain('quality-assurance:');
82+
expect(content).toContain('deployment:');
83+
expect(content).toContain('permissions:');
84+
});
85+
});
86+
87+
describe('generateIgnoreFile', () => {
88+
it('should create .contextignore when it does not exist', async () => {
89+
const result = await generator.generateIgnoreFile();
90+
expect(result).toBe(true);
91+
expect(mockFs.writeFile).toHaveBeenCalledWith(
92+
'.contextignore',
93+
expect.stringContaining('node_modules/')
94+
);
95+
});
96+
97+
it('should not create .contextignore when it already exists', async () => {
98+
mockFs.access.mockResolvedValue(undefined);
99+
const result = await generator.generateIgnoreFile();
100+
expect(result).toBe(false);
101+
expect(mockFs.writeFile).not.toHaveBeenCalled();
102+
});
103+
104+
it('should throw error when file creation fails', async () => {
105+
mockFs.writeFile.mockRejectedValue(new Error('Permission denied'));
106+
await expect(generator.generateIgnoreFile()).rejects.toThrow('Failed to generate ignore file');
107+
});
108+
109+
it('should include common ignore patterns', async () => {
110+
await generator.generateIgnoreFile();
111+
const writeFileCall = mockFs.writeFile.mock.calls[0];
112+
const content = writeFileCall[1] as string;
113+
114+
expect(content).toContain('node_modules/');
115+
expect(content).toContain('dist/');
116+
expect(content).toContain('.env');
117+
expect(content).toContain('*.log');
118+
});
119+
});
120+
121+
describe('generate', () => {
122+
it('should generate all files when none exist', async () => {
123+
const result = await generator.generate();
124+
expect(result).toEqual({
125+
dirCreated: true,
126+
indexCreated: true,
127+
ignoreCreated: true
128+
});
129+
});
130+
131+
it('should not regenerate existing files', async () => {
132+
mockFs.access.mockResolvedValue(undefined);
133+
const result = await generator.generate();
134+
expect(result).toEqual({
135+
dirCreated: false,
136+
indexCreated: false,
137+
ignoreCreated: false
138+
});
139+
});
140+
141+
it('should handle mixed existing and non-existing files', async () => {
142+
// Mock only directory exists
143+
mockFs.access.mockImplementation((path) => {
144+
if (path === '.context') {
145+
return Promise.resolve(undefined);
146+
}
147+
return Promise.reject(new Error('ENOENT'));
148+
});
149+
150+
const result = await generator.generate();
151+
expect(result).toEqual({
152+
dirCreated: false,
153+
indexCreated: true,
154+
ignoreCreated: true
155+
});
156+
});
157+
});
158+
159+
describe('custom context directory', () => {
160+
it('should use custom directory path when provided', async () => {
161+
const customPath = path.join('custom', '.context');
162+
const customGenerator = new ContextGenerator(customPath);
163+
await customGenerator.generate();
164+
165+
expect(mockFs.mkdir).toHaveBeenCalledWith(customPath, { recursive: true });
166+
expect(mockFs.writeFile).toHaveBeenCalledWith(
167+
path.join(customPath, 'index.md'),
168+
expect.any(String)
169+
);
170+
});
171+
});
172+
});

test/lib/ContextLinter.test.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { jest } from '@jest/globals';
2+
import { ContextLinter } from '../../src/lib/ContextLinter.js';
3+
4+
// Mock fs/promises module
5+
jest.mock('fs/promises', () => ({
6+
access: jest.fn(),
7+
readFile: jest.fn(),
8+
writeFile: jest.fn()
9+
}));
10+
11+
import * as fs from 'fs/promises';
12+
13+
describe('ContextLinter', () => {
14+
let linter: ContextLinter;
15+
const mockFs = fs as jest.Mocked<typeof fs>;
16+
17+
beforeEach(() => {
18+
linter = new ContextLinter();
19+
jest.clearAllMocks();
20+
// Default successful mocks
21+
mockFs.access.mockResolvedValue(undefined);
22+
mockFs.writeFile.mockResolvedValue(undefined);
23+
});
24+
25+
describe('lint', () => {
26+
it('should return no issues for valid content', async () => {
27+
const validContent = `---
28+
module-name: test-module
29+
version: 1.0.0
30+
description: Test description
31+
technologies: [TypeScript, Node.js]
32+
architecture: MVC
33+
`;
34+
mockFs.readFile.mockResolvedValue(validContent);
35+
36+
const result = await linter.lint();
37+
expect(result.issues).toHaveLength(0);
38+
expect(result.fixedCount).toBe(0);
39+
});
40+
41+
it('should detect missing frontmatter delimiter', async () => {
42+
const invalidContent = `
43+
module-name: test-module
44+
version: 1.0.0
45+
description: Test description
46+
technologies: [TypeScript, Node.js]
47+
architecture: MVC
48+
`;
49+
mockFs.readFile.mockResolvedValue(invalidContent);
50+
51+
const result = await linter.lint();
52+
expect(result.issues).toHaveLength(1);
53+
expect(result.issues[0].message).toContain('Missing frontmatter delimiter');
54+
});
55+
56+
it('should detect missing required fields', async () => {
57+
const incompleteContent = `---
58+
module-name: test-module
59+
version: 1.0.0
60+
`;
61+
mockFs.readFile.mockResolvedValue(incompleteContent);
62+
63+
const result = await linter.lint();
64+
expect(result.issues.length).toBeGreaterThan(0);
65+
expect(result.issues.some(i => i.message.includes('Missing required field: description'))).toBe(true);
66+
expect(result.issues.some(i => i.message.includes('Missing required field: technologies'))).toBe(true);
67+
expect(result.issues.some(i => i.message.includes('Missing required field: architecture'))).toBe(true);
68+
});
69+
70+
it('should detect invalid version format', async () => {
71+
const invalidVersionContent = `---
72+
module-name: test-module
73+
version: 1.0
74+
description: Test description
75+
technologies: [TypeScript, Node.js]
76+
architecture: MVC
77+
`;
78+
mockFs.readFile.mockResolvedValue(invalidVersionContent);
79+
80+
const result = await linter.lint();
81+
expect(result.issues).toHaveLength(1);
82+
expect(result.issues[0].message).toBe('Invalid version format');
83+
});
84+
85+
describe('auto-fix mode', () => {
86+
it('should fix missing frontmatter delimiter', async () => {
87+
const invalidContent = `
88+
module-name: test-module
89+
version: 1.0.0
90+
description: Test description
91+
technologies: [TypeScript, Node.js]
92+
architecture: MVC
93+
`;
94+
mockFs.readFile.mockResolvedValue(invalidContent);
95+
96+
const result = await linter.lint(true);
97+
expect(result.fixedCount).toBe(1);
98+
expect(mockFs.writeFile).toHaveBeenCalled();
99+
const writtenContent = mockFs.writeFile.mock.calls[0][1] as string;
100+
expect(writtenContent.startsWith('---')).toBe(true);
101+
});
102+
103+
it('should fix missing required fields', async () => {
104+
const incompleteContent = `---
105+
module-name: test-module
106+
version: 1.0.0
107+
`;
108+
mockFs.readFile.mockResolvedValue(incompleteContent);
109+
110+
const result = await linter.lint(true);
111+
expect(result.fixedCount).toBe(3); // description, technologies, architecture
112+
expect(mockFs.writeFile).toHaveBeenCalled();
113+
const writtenContent = mockFs.writeFile.mock.calls[0][1] as string;
114+
expect(writtenContent).toContain('description:');
115+
expect(writtenContent).toContain('technologies:');
116+
expect(writtenContent).toContain('architecture:');
117+
});
118+
});
119+
120+
describe('error handling', () => {
121+
it('should throw error when context directory does not exist', async () => {
122+
mockFs.access.mockRejectedValue(new Error('Directory not found'));
123+
124+
await expect(linter.lint()).rejects.toThrow('Linting failed');
125+
});
126+
127+
it('should throw error when index.md cannot be read', async () => {
128+
mockFs.readFile.mockRejectedValue(new Error('File read error'));
129+
130+
await expect(linter.lint()).rejects.toThrow('Linting failed');
131+
});
132+
});
133+
});
134+
});

0 commit comments

Comments
 (0)