Skip to content

Commit 2bfc5e7

Browse files
super3claude
andcommitted
Add comprehensive test suite with Jest and GitHub Actions
- Added Jest test framework with 29 unit tests - Achieved 100% code coverage - Refactored core logic into testable lib/mdtail.js module - Created GitHub Actions workflow for automated testing - Configured Coveralls integration for coverage reporting - Tests cover argument parsing, file handling, navigation, and display - Added test scripts: test, test:watch, test:coverage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ba761fd commit 2bfc5e7

File tree

7 files changed

+4826
-3
lines changed

7 files changed

+4826
-3
lines changed

.github/workflows/test.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node.js
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: '22.x'
21+
cache: 'npm'
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Run tests
27+
run: npm test
28+
29+
- name: Generate coverage report
30+
run: npm run test:coverage
31+
32+
- name: Upload coverage to Coveralls
33+
uses: coverallsapp/github-action@v2
34+
with:
35+
github-token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
node_modules/
2+
coverage/
23
.DS_Store
34
*.log
4-
.env
5+
.env
6+
.vscode/
7+
.idea/

jest.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
collectCoverageFrom: [
4+
'lib/**/*.js',
5+
'!lib/**/*.test.js'
6+
],
7+
coverageDirectory: 'coverage',
8+
coverageReporters: ['text', 'lcov', 'html'],
9+
testMatch: [
10+
'**/test/**/*.test.js'
11+
],
12+
verbose: true
13+
};

lib/mdtail.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
class MdTail {
5+
constructor() {
6+
this.files = [];
7+
this.currentTabIndex = 0;
8+
}
9+
10+
parseArguments(args) {
11+
const helpFlags = ['-h', '--help'];
12+
13+
if (args.some(arg => helpFlags.includes(arg))) {
14+
return { showHelp: true };
15+
}
16+
17+
return { showHelp: false };
18+
}
19+
20+
expandFiles(args, currentDir = process.cwd()) {
21+
const files = [];
22+
23+
if (args.length === 0) {
24+
// Default to TODO.md in current directory
25+
const defaultFile = path.join(currentDir, 'TODO.md');
26+
if (fs.existsSync(defaultFile)) {
27+
files.push(defaultFile);
28+
}
29+
} else {
30+
args.forEach(arg => {
31+
if (arg.includes('*')) {
32+
// Handle wildcards
33+
const mdFiles = fs.readdirSync(currentDir)
34+
.filter(file => file.endsWith('.md'))
35+
.sort()
36+
.map(f => path.resolve(currentDir, f));
37+
files.push(...mdFiles);
38+
} else {
39+
// Resolve individual file paths
40+
const filePath = path.resolve(currentDir, arg);
41+
if (fs.existsSync(filePath) && filePath.endsWith('.md')) {
42+
files.push(filePath);
43+
}
44+
}
45+
});
46+
}
47+
48+
// Remove duplicates
49+
return [...new Set(files)];
50+
}
51+
52+
validateFiles(files) {
53+
if (!Array.isArray(files) || files.length === 0) {
54+
throw new Error('No markdown files found to watch');
55+
}
56+
return true;
57+
}
58+
59+
renderTabs(files, currentIndex, width = 80) {
60+
if (files.length <= 1) return '';
61+
62+
const tabs = files.map((file, index) => {
63+
const filename = path.basename(file);
64+
const isActive = index === currentIndex;
65+
66+
if (isActive) {
67+
return `[${filename}]`;
68+
} else {
69+
return ` ${filename} `;
70+
}
71+
});
72+
73+
const tabLine = tabs.join(' │ ');
74+
return `${tabLine}\n${'─'.repeat(width)}\n`;
75+
}
76+
77+
formatContent(content, filename, width = 80) {
78+
const lines = [];
79+
lines.push('\n' + '═'.repeat(width));
80+
lines.push(filename.toUpperCase());
81+
lines.push('═'.repeat(width) + '\n');
82+
lines.push(content);
83+
lines.push('\n' + '═'.repeat(width));
84+
return lines.join('\n');
85+
}
86+
87+
getHelpText() {
88+
return `
89+
mdtail - Terminal markdown viewer with live refresh
90+
91+
Usage:
92+
mdtail [file1.md] [file2.md] ... Watch specific markdown files
93+
mdtail Watch TODO.md (default)
94+
mdtail -h, --help Show this help message
95+
96+
Navigation:
97+
← / → Arrow Keys Switch between tabs
98+
Ctrl+C Exit
99+
100+
Examples:
101+
mdtail README.md Watch README.md
102+
mdtail todo.md notes.md Watch multiple files with tabs
103+
mdtail *.md Watch all markdown files in tabs
104+
`;
105+
}
106+
107+
navigateTab(direction) {
108+
if (this.files.length <= 1) return false;
109+
110+
if (direction === 'left') {
111+
this.currentTabIndex = (this.currentTabIndex - 1 + this.files.length) % this.files.length;
112+
} else if (direction === 'right') {
113+
this.currentTabIndex = (this.currentTabIndex + 1) % this.files.length;
114+
} else {
115+
return false;
116+
}
117+
118+
return true;
119+
}
120+
}
121+
122+
module.exports = MdTail;

0 commit comments

Comments
 (0)