gh-here 3.0.3 → 3.1.0

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.
Files changed (41) hide show
  1. package/.env +0 -0
  2. package/.playwright-mcp/fixed-alignment.png +0 -0
  3. package/.playwright-mcp/fixed-layout.png +0 -0
  4. package/.playwright-mcp/gh-here-home-header-table.png +0 -0
  5. package/.playwright-mcp/gh-here-home.png +0 -0
  6. package/.playwright-mcp/line-selection-multiline.png +0 -0
  7. package/.playwright-mcp/line-selection-test-after.png +0 -0
  8. package/.playwright-mcp/line-selection-test-before.png +0 -0
  9. package/.playwright-mcp/page-2026-01-03T17-58-21-336Z.png +0 -0
  10. package/lib/constants.js +25 -15
  11. package/lib/content-search.js +212 -0
  12. package/lib/error-handler.js +39 -28
  13. package/lib/file-utils.js +438 -287
  14. package/lib/git.js +10 -54
  15. package/lib/gitignore.js +70 -41
  16. package/lib/renderers.js +15 -19
  17. package/lib/server.js +70 -193
  18. package/lib/symbol-parser.js +600 -0
  19. package/package.json +1 -1
  20. package/public/app.js +134 -68
  21. package/public/js/constants.js +50 -34
  22. package/public/js/content-search-handler.js +551 -0
  23. package/public/js/file-viewer.js +437 -0
  24. package/public/js/focus-mode.js +280 -0
  25. package/public/js/inline-search.js +659 -0
  26. package/public/js/modal-manager.js +14 -28
  27. package/public/js/symbol-outline.js +454 -0
  28. package/public/js/utils.js +152 -94
  29. package/public/styles.css +2049 -296
  30. package/.claude/settings.local.json +0 -30
  31. package/SAMPLE.md +0 -287
  32. package/lib/validation.js +0 -77
  33. package/public/app.js.backup +0 -1902
  34. package/public/js/draft-manager.js +0 -36
  35. package/public/js/editor-manager.js +0 -159
  36. package/test.js +0 -138
  37. package/tests/draftManager.test.js +0 -241
  38. package/tests/fileTypeDetection.test.js +0 -111
  39. package/tests/httpService.test.js +0 -268
  40. package/tests/languageDetection.test.js +0 -145
  41. package/tests/pathUtils.test.js +0 -136
@@ -1,36 +0,0 @@
1
- /**
2
- * Draft management for file editing
3
- */
4
-
5
- import { STORAGE_KEYS } from './constants.js';
6
-
7
- export const DraftManager = {
8
- saveDraft(filePath, content) {
9
- localStorage.setItem(`${STORAGE_KEYS.DRAFT_PREFIX}${filePath}`, content);
10
- },
11
-
12
- loadDraft(filePath) {
13
- return localStorage.getItem(`${STORAGE_KEYS.DRAFT_PREFIX}${filePath}`);
14
- },
15
-
16
- clearDraft(filePath) {
17
- localStorage.removeItem(`${STORAGE_KEYS.DRAFT_PREFIX}${filePath}`);
18
- },
19
-
20
- hasDraftChanges(filePath, originalContent) {
21
- const draft = this.loadDraft(filePath);
22
- return draft !== null && draft !== originalContent;
23
- },
24
-
25
- getAllDrafts() {
26
- const drafts = {};
27
- for (let i = 0; i < localStorage.length; i++) {
28
- const key = localStorage.key(i);
29
- if (key.startsWith(STORAGE_KEYS.DRAFT_PREFIX)) {
30
- const filePath = key.replace(STORAGE_KEYS.DRAFT_PREFIX, '');
31
- drafts[filePath] = localStorage.getItem(key);
32
- }
33
- }
34
- return drafts;
35
- }
36
- };
@@ -1,159 +0,0 @@
1
- /**
2
- * Monaco Editor management
3
- */
4
-
5
- import { CONFIG, EDITOR_OPTIONS } from './constants.js';
6
- import { getLanguageFromExtension } from './utils.js';
7
- import { DraftManager } from './draft-manager.js';
8
- import { showDraftDialog } from './modal-manager.js';
9
- import { showNotification } from './notification.js';
10
-
11
- export class EditorManager {
12
- constructor(theme) {
13
- this.fileEditor = null;
14
- this.newFileEditor = null;
15
- this.theme = theme;
16
- this.ready = false;
17
- this.init();
18
- }
19
-
20
- init() {
21
- if (typeof require === 'undefined') {
22
- return;
23
- }
24
-
25
- require.config({ paths: { vs: CONFIG.MONACO_CDN } });
26
-
27
- require(['vs/editor/editor.main'], () => {
28
- self.MonacoEnvironment = {
29
- getWorker: () => undefined
30
- };
31
-
32
- const monacoTheme = this.theme === 'dark' ? 'vs-dark' : 'vs';
33
- monaco.editor.setTheme(monacoTheme);
34
-
35
- this.initializeNewFileEditor();
36
- this.ready = true;
37
- window.monacoReady = true;
38
- });
39
- }
40
-
41
- initializeNewFileEditor() {
42
- const container = document.getElementById('new-file-content');
43
- if (!container) {
44
- return;
45
- }
46
-
47
- const monacoTheme = this.theme === 'dark' ? 'vs-dark' : 'vs';
48
- this.newFileEditor = monaco.editor.create(container, {
49
- ...EDITOR_OPTIONS,
50
- value: '',
51
- language: 'plaintext',
52
- theme: monacoTheme
53
- });
54
- }
55
-
56
- async createFileEditor(container, filePath, originalContent) {
57
- if (!this.ready) {
58
- await this.waitForReady();
59
- }
60
-
61
- const filename = filePath.split('/').pop() || 'file.txt';
62
- const language = getLanguageFromExtension(filename);
63
- const availableLanguages = monaco.languages.getLanguages().map(lang => lang.id);
64
- const validLanguage = availableLanguages.includes(language) ? language : 'plaintext';
65
- const monacoTheme = this.theme === 'dark' ? 'vs-dark' : 'vs';
66
-
67
- let contentToLoad = originalContent;
68
-
69
- if (DraftManager.hasDraftChanges(filePath, originalContent)) {
70
- const draftChoice = await showDraftDialog(filePath);
71
- if (draftChoice === 'load') {
72
- contentToLoad = DraftManager.loadDraft(filePath);
73
- } else if (draftChoice === 'discard') {
74
- DraftManager.clearDraft(filePath);
75
- }
76
- } else {
77
- const draft = DraftManager.loadDraft(filePath);
78
- if (draft && draft === originalContent) {
79
- DraftManager.clearDraft(filePath);
80
- }
81
- }
82
-
83
- if (!this.fileEditor) {
84
- this.fileEditor = monaco.editor.create(container, {
85
- ...EDITOR_OPTIONS,
86
- value: contentToLoad,
87
- language: validLanguage,
88
- theme: monacoTheme
89
- });
90
-
91
- this.fileEditor.onDidChangeModelContent(() => {
92
- DraftManager.saveDraft(filePath, this.fileEditor.getValue());
93
- });
94
- } else {
95
- this.fileEditor.setValue(contentToLoad);
96
- const model = this.fileEditor.getModel();
97
- if (model) {
98
- monaco.editor.setModelLanguage(model, validLanguage);
99
- }
100
- }
101
-
102
- setTimeout(() => this.fileEditor.layout(), 50);
103
-
104
- return this.fileEditor;
105
- }
106
-
107
- waitForReady() {
108
- return new Promise(resolve => {
109
- const check = () => {
110
- if (this.ready) {
111
- resolve();
112
- } else {
113
- setTimeout(check, 100);
114
- }
115
- };
116
- check();
117
- });
118
- }
119
-
120
- updateLanguage(filename) {
121
- if (this.newFileEditor) {
122
- const language = getLanguageFromExtension(filename);
123
- const model = this.newFileEditor.getModel();
124
- if (model) {
125
- monaco.editor.setModelLanguage(model, language);
126
- }
127
- }
128
- }
129
-
130
- toggleWordWrap(editor) {
131
- if (!editor) {
132
- return false;
133
- }
134
-
135
- const currentWrap = editor.getOption(monaco.editor.EditorOption.wordWrap);
136
- const newWrap = currentWrap === 'off' ? 'on' : 'off';
137
- editor.updateOptions({ wordWrap: newWrap });
138
- return newWrap === 'on';
139
- }
140
-
141
- getValue(editorType = 'file') {
142
- const editor = editorType === 'file' ? this.fileEditor : this.newFileEditor;
143
- return editor ? editor.getValue() : '';
144
- }
145
-
146
- focus(editorType = 'file') {
147
- const editor = editorType === 'file' ? this.fileEditor : this.newFileEditor;
148
- if (editor) {
149
- editor.focus();
150
- }
151
- }
152
-
153
- layout(editorType = 'file') {
154
- const editor = editorType === 'file' ? this.fileEditor : this.newFileEditor;
155
- if (editor) {
156
- editor.layout();
157
- }
158
- }
159
- }
package/test.js DELETED
@@ -1,138 +0,0 @@
1
- /**
2
- * Lightweight smoke tests for gh-here
3
- * Run with: node test.js
4
- */
5
-
6
- const { chromium } = require('playwright');
7
- const { spawn } = require('child_process');
8
-
9
- const TEST_PORT = 5556; // Use different port to avoid conflicts
10
- const BASE_URL = `http://localhost:${TEST_PORT}`;
11
-
12
- async function sleep(ms) {
13
- return new Promise(resolve => setTimeout(resolve, ms));
14
- }
15
-
16
- async function runTests() {
17
- console.log('Starting gh-here server...');
18
-
19
- // Start server
20
- const server = spawn('node', ['bin/gh-here.js', `--port=${TEST_PORT}`], {
21
- cwd: __dirname,
22
- stdio: 'pipe'
23
- });
24
-
25
- // Wait for server to start
26
- await sleep(2000);
27
-
28
- let browser;
29
- let failures = 0;
30
-
31
- try {
32
- console.log('Launching browser...');
33
- browser = await chromium.launch({ headless: true });
34
- const context = await browser.newContext();
35
- const page = await context.newPage();
36
-
37
- // Test 1: Root page loads
38
- console.log('\nāœ“ Test 1: Root page loads');
39
- await page.goto(BASE_URL);
40
- const title = await page.title();
41
- if (!title.includes('gh-here')) {
42
- console.error(' āœ— FAILED: Page title incorrect');
43
- failures++;
44
- }
45
-
46
- // Test 2: File tree exists (may be hidden on root)
47
- console.log('āœ“ Test 2: File tree element exists');
48
- const fileTreeExists = await page.$('#file-tree');
49
- if (!fileTreeExists) {
50
- console.error(' āœ— FAILED: File tree element not found');
51
- failures++;
52
- }
53
-
54
- // Test 3: Navigate to a file and check line numbers display correctly
55
- console.log('āœ“ Test 3: File view with line numbers');
56
- await page.goto(`${BASE_URL}/?path=lib/renderers.js`);
57
- await page.waitForSelector('.line-container', { timeout: 5000 });
58
-
59
- // Check that line numbers are in a vertical column (not nested)
60
- const lineContainers = await page.$$('.line-container');
61
- if (lineContainers.length < 10) {
62
- console.error(' āœ— FAILED: Not enough line containers found');
63
- failures++;
64
- }
65
-
66
- // Check line 1 and line 2 have sequential numbers
67
- const line1 = await page.$('.line-container[data-line="1"]');
68
- const line2 = await page.$('.line-container[data-line="2"]');
69
- if (!line1 || !line2) {
70
- console.error(' āœ— FAILED: Line containers missing data-line attributes');
71
- failures++;
72
- }
73
-
74
- // Verify line numbers are not nested (check display property)
75
- const line1Display = await page.$eval('.line-container[data-line="1"]',
76
- el => window.getComputedStyle(el).display
77
- );
78
- if (line1Display !== 'block') {
79
- console.error(` āœ— FAILED: Line containers should have display:block, got ${line1Display}`);
80
- failures++;
81
- }
82
-
83
- // Test 4: Check if modified files show diff button
84
- console.log('āœ“ Test 4: Modified files show diff button');
85
- await page.goto(BASE_URL);
86
- const diffButtons = await page.$$('.diff-btn');
87
- // Should have at least one diff button if there are modified files
88
- console.log(` Found ${diffButtons.length} diff buttons`);
89
-
90
- // Test 5: Gitignore toggle exists and is clickable
91
- console.log('āœ“ Test 5: Gitignore toggle button');
92
- const gitignoreToggle = await page.$('#gitignore-toggle');
93
- if (!gitignoreToggle) {
94
- console.error(' āœ— FAILED: Gitignore toggle button not found');
95
- failures++;
96
- }
97
-
98
- // Test 6: Theme toggle works
99
- console.log('āœ“ Test 6: Theme toggle');
100
- const themeToggle = await page.$('#theme-toggle');
101
- if (!themeToggle) {
102
- console.error(' āœ— FAILED: Theme toggle button not found');
103
- failures++;
104
- }
105
-
106
- // Test 7: Search functionality
107
- console.log('āœ“ Test 7: Search input exists');
108
- const searchInput = await page.$('#file-search, #root-file-search');
109
- if (!searchInput) {
110
- console.error(' āœ— FAILED: Search input not found');
111
- failures++;
112
- }
113
-
114
- console.log('\n' + '='.repeat(50));
115
- if (failures === 0) {
116
- console.log('āœ“ All tests passed!');
117
- } else {
118
- console.log(`āœ— ${failures} test(s) failed`);
119
- }
120
- console.log('='.repeat(50) + '\n');
121
-
122
- } catch (error) {
123
- console.error('\nāœ— Test failed with error:', error.message);
124
- failures++;
125
- } finally {
126
- if (browser) {
127
- await browser.close();
128
- }
129
-
130
- // Kill server
131
- server.kill();
132
-
133
- // Exit with appropriate code
134
- process.exit(failures > 0 ? 1 : 0);
135
- }
136
- }
137
-
138
- runTests();
@@ -1,241 +0,0 @@
1
- // Tests for DraftManager utility
2
- // Run with: node tests/draftManager.test.js
3
-
4
- // Mock localStorage for testing
5
- class MockLocalStorage {
6
- constructor() {
7
- this.store = {};
8
- }
9
-
10
- getItem(key) {
11
- return this.store.hasOwnProperty(key) ? this.store[key] : null;
12
- }
13
-
14
- setItem(key, value) {
15
- this.store[key] = String(value);
16
- }
17
-
18
- removeItem(key) {
19
- delete this.store[key];
20
- }
21
-
22
- clear() {
23
- this.store = {};
24
- }
25
-
26
- key(index) {
27
- const keys = Object.keys(this.store);
28
- return keys[index] || null;
29
- }
30
-
31
- get length() {
32
- return Object.keys(this.store).length;
33
- }
34
- }
35
-
36
- // Create testable version of DraftManager with injected storage
37
- function createDraftManager(storage) {
38
- return {
39
- STORAGE_PREFIX: 'gh-here-draft-',
40
-
41
- saveDraft(filePath, content) {
42
- storage.setItem(`${this.STORAGE_PREFIX}${filePath}`, content);
43
- },
44
-
45
- loadDraft(filePath) {
46
- return storage.getItem(`${this.STORAGE_PREFIX}${filePath}`);
47
- },
48
-
49
- clearDraft(filePath) {
50
- storage.removeItem(`${this.STORAGE_PREFIX}${filePath}`);
51
- },
52
-
53
- hasDraftChanges(filePath, originalContent) {
54
- const draft = this.loadDraft(filePath);
55
- return draft !== null && draft !== originalContent;
56
- },
57
-
58
- getAllDrafts() {
59
- const drafts = {};
60
- for (let i = 0; i < storage.length; i++) {
61
- const key = storage.key(i);
62
- if (key && key.startsWith(this.STORAGE_PREFIX)) {
63
- const filePath = key.replace(this.STORAGE_PREFIX, '');
64
- drafts[filePath] = storage.getItem(key);
65
- }
66
- }
67
- return drafts;
68
- }
69
- };
70
- }
71
-
72
- // Simple test framework
73
- function test(name, fn) {
74
- try {
75
- fn();
76
- console.log(`āœ… ${name}`);
77
- } catch (error) {
78
- console.log(`āŒ ${name}: ${error.message}`);
79
- }
80
- }
81
-
82
- function assertEqual(actual, expected, message = '') {
83
- if (actual !== expected) {
84
- throw new Error(`Expected "${expected}" but got "${actual}". ${message}`);
85
- }
86
- }
87
-
88
- function assertNull(actual, message = '') {
89
- if (actual !== null) {
90
- throw new Error(`Expected null but got "${actual}". ${message}`);
91
- }
92
- }
93
-
94
- function assertTrue(actual, message = '') {
95
- if (!actual) {
96
- throw new Error(`Expected truthy but got "${actual}". ${message}`);
97
- }
98
- }
99
-
100
- function assertFalse(actual, message = '') {
101
- if (actual) {
102
- throw new Error(`Expected falsy but got "${actual}". ${message}`);
103
- }
104
- }
105
-
106
- // Test cases
107
- console.log('🧪 Testing Draft Manager\n');
108
-
109
- test('saves and loads drafts correctly', () => {
110
- const storage = new MockLocalStorage();
111
- const draftManager = createDraftManager(storage);
112
-
113
- const filePath = '/src/app.js';
114
- const content = 'console.log("hello world");';
115
-
116
- // Initially no draft
117
- assertNull(draftManager.loadDraft(filePath));
118
-
119
- // Save draft
120
- draftManager.saveDraft(filePath, content);
121
-
122
- // Load draft
123
- assertEqual(draftManager.loadDraft(filePath), content);
124
- });
125
-
126
- test('clears drafts correctly', () => {
127
- const storage = new MockLocalStorage();
128
- const draftManager = createDraftManager(storage);
129
-
130
- const filePath = '/src/app.js';
131
- const content = 'console.log("test");';
132
-
133
- // Save and verify
134
- draftManager.saveDraft(filePath, content);
135
- assertEqual(draftManager.loadDraft(filePath), content);
136
-
137
- // Clear and verify
138
- draftManager.clearDraft(filePath);
139
- assertNull(draftManager.loadDraft(filePath));
140
- });
141
-
142
- test('detects draft changes correctly', () => {
143
- const storage = new MockLocalStorage();
144
- const draftManager = createDraftManager(storage);
145
-
146
- const filePath = '/src/app.js';
147
- const originalContent = 'const x = 1;';
148
- const draftContent = 'const x = 2;';
149
-
150
- // No draft initially
151
- assertFalse(draftManager.hasDraftChanges(filePath, originalContent));
152
-
153
- // Save draft with different content
154
- draftManager.saveDraft(filePath, draftContent);
155
- assertTrue(draftManager.hasDraftChanges(filePath, originalContent));
156
-
157
- // Save draft with same content
158
- draftManager.saveDraft(filePath, originalContent);
159
- assertFalse(draftManager.hasDraftChanges(filePath, originalContent));
160
- });
161
-
162
- test('gets all drafts correctly', () => {
163
- const storage = new MockLocalStorage();
164
- const draftManager = createDraftManager(storage);
165
-
166
- // Add some non-draft items to storage
167
- storage.setItem('other-key', 'other-value');
168
-
169
- // Add drafts
170
- draftManager.saveDraft('/src/app.js', 'app content');
171
- draftManager.saveDraft('/src/utils.js', 'utils content');
172
- draftManager.saveDraft('/README.md', 'readme content');
173
-
174
- const allDrafts = draftManager.getAllDrafts();
175
-
176
- // Should only return draft items
177
- assertEqual(Object.keys(allDrafts).length, 3);
178
- assertEqual(allDrafts['/src/app.js'], 'app content');
179
- assertEqual(allDrafts['/src/utils.js'], 'utils content');
180
- assertEqual(allDrafts['/README.md'], 'readme content');
181
-
182
- // Should not include non-draft items
183
- assertEqual(allDrafts['other-key'], undefined);
184
- });
185
-
186
- test('handles multiple drafts independently', () => {
187
- const storage = new MockLocalStorage();
188
- const draftManager = createDraftManager(storage);
189
-
190
- const file1 = '/src/app.js';
191
- const file2 = '/src/utils.js';
192
- const content1 = 'app content';
193
- const content2 = 'utils content';
194
-
195
- // Save drafts for different files
196
- draftManager.saveDraft(file1, content1);
197
- draftManager.saveDraft(file2, content2);
198
-
199
- // Each file has its own draft
200
- assertEqual(draftManager.loadDraft(file1), content1);
201
- assertEqual(draftManager.loadDraft(file2), content2);
202
-
203
- // Clear one draft, other remains
204
- draftManager.clearDraft(file1);
205
- assertNull(draftManager.loadDraft(file1));
206
- assertEqual(draftManager.loadDraft(file2), content2);
207
- });
208
-
209
- test('uses correct storage prefix', () => {
210
- const storage = new MockLocalStorage();
211
- const draftManager = createDraftManager(storage);
212
-
213
- const filePath = '/src/app.js';
214
- const content = 'test content';
215
-
216
- draftManager.saveDraft(filePath, content);
217
-
218
- // Check that correct key is used in storage
219
- const expectedKey = 'gh-here-draft-/src/app.js';
220
- assertEqual(storage.getItem(expectedKey), content);
221
-
222
- // Verify storage length
223
- assertEqual(storage.length, 1);
224
- });
225
-
226
- test('handles empty content correctly', () => {
227
- const storage = new MockLocalStorage();
228
- const draftManager = createDraftManager(storage);
229
-
230
- const filePath = '/empty.js';
231
- const emptyContent = '';
232
-
233
- draftManager.saveDraft(filePath, emptyContent);
234
- assertEqual(draftManager.loadDraft(filePath), ''); // localStorage stores empty string as empty string
235
-
236
- // Empty content should be detected as different from non-empty
237
- assertTrue(draftManager.hasDraftChanges(filePath, 'non-empty'), 'Empty draft should be different from non-empty original');
238
- assertFalse(draftManager.hasDraftChanges(filePath, ''));
239
- });
240
-
241
- console.log('\nšŸŽ‰ Draft Manager tests complete!');
@@ -1,111 +0,0 @@
1
- // Simple unit tests for file type detection
2
- // Run with: node tests/fileTypeDetection.test.js
3
-
4
- // Import the functions from our file-utils module
5
- const { isImageFile, isBinaryFile, isTextFile } = require('../lib/file-utils');
6
-
7
- function test(description, testFn) {
8
- try {
9
- testFn();
10
- console.log(`āœ… ${description}`);
11
- } catch (error) {
12
- console.log(`āŒ ${description}`);
13
- console.log(` Error: ${error.message}`);
14
- }
15
- }
16
-
17
- function assert(condition, message) {
18
- if (!condition) {
19
- throw new Error(message || 'Assertion failed');
20
- }
21
- }
22
-
23
- // Test isImageFile function
24
- test('should detect PNG files as images', () => {
25
- assert(isImageFile('photo.png'), 'PNG should be detected as image');
26
- assert(isImageFile('PNG'), 'Extension-only PNG should work');
27
- });
28
-
29
- test('should detect common image formats', () => {
30
- const imageFiles = ['test.jpg', 'test.jpeg', 'test.gif', 'test.svg', 'test.webp', 'test.bmp', 'test.tiff', 'test.ico'];
31
- imageFiles.forEach(file => {
32
- assert(isImageFile(file), `${file} should be detected as image`);
33
- });
34
- });
35
-
36
- test('should not detect text files as images', () => {
37
- const textFiles = ['readme.md', 'script.js', 'style.css', 'config.json'];
38
- textFiles.forEach(file => {
39
- assert(!isImageFile(file), `${file} should not be detected as image`);
40
- });
41
- });
42
-
43
- // Test isBinaryFile function
44
- test('should detect images as binary files', () => {
45
- assert(isBinaryFile('photo.png'), 'Images should be binary');
46
- assert(isBinaryFile('animation.gif'), 'GIFs should be binary');
47
- });
48
-
49
- test('should detect archives as binary files', () => {
50
- const archives = ['file.zip', 'backup.tar', 'compressed.gz', 'archive.rar', 'package.7z'];
51
- archives.forEach(file => {
52
- assert(isBinaryFile(file), `${file} should be detected as binary`);
53
- });
54
- });
55
-
56
- test('should detect executables as binary files', () => {
57
- const executables = ['program.exe', 'binary.bin', 'application.app'];
58
- executables.forEach(file => {
59
- assert(isBinaryFile(file), `${file} should be detected as binary`);
60
- });
61
- });
62
-
63
- test('should detect documents as binary files', () => {
64
- const docs = ['document.pdf', 'spreadsheet.xlsx', 'presentation.pptx'];
65
- docs.forEach(file => {
66
- assert(isBinaryFile(file), `${file} should be detected as binary`);
67
- });
68
- });
69
-
70
- test('should not detect text files as binary', () => {
71
- const textFiles = ['readme.md', 'script.js', 'style.css', 'data.json', 'config.yml', 'index.html'];
72
- textFiles.forEach(file => {
73
- assert(!isBinaryFile(file), `${file} should not be detected as binary`);
74
- });
75
- });
76
-
77
- // Test isTextFile function
78
- test('should detect common text files', () => {
79
- const textFiles = ['readme.md', 'script.js', 'style.css', 'data.json', 'config.yml', 'index.html', 'app.py', 'main.go'];
80
- textFiles.forEach(file => {
81
- assert(isTextFile(file), `${file} should be detected as text`);
82
- });
83
- });
84
-
85
- test('should not detect binary files as text', () => {
86
- const binaryFiles = ['photo.png', 'archive.zip', 'program.exe', 'document.pdf'];
87
- binaryFiles.forEach(file => {
88
- assert(!isTextFile(file), `${file} should not be detected as text`);
89
- });
90
- });
91
-
92
- // Test edge cases
93
- test('should handle files without extensions', () => {
94
- assert(isTextFile('README'), 'Files without extensions should default to text');
95
- assert(isTextFile('Makefile'), 'Common text files without extensions should be text');
96
- });
97
-
98
- test('should handle extension-only input', () => {
99
- assert(isImageFile('png'), 'Should handle bare extensions');
100
- assert(isBinaryFile('exe'), 'Should handle bare extensions for binary');
101
- assert(isTextFile('js'), 'Should handle bare extensions for text');
102
- });
103
-
104
- test('should be case insensitive', () => {
105
- assert(isImageFile('PHOTO.PNG'), 'Should handle uppercase extensions');
106
- assert(isBinaryFile('ARCHIVE.ZIP'), 'Should handle uppercase extensions');
107
- assert(isTextFile('SCRIPT.JS'), 'Should handle uppercase extensions');
108
- });
109
-
110
- // Run all tests
111
- console.log('Running file type detection tests...\n');