gh-here 1.0.2 → 1.0.5

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.
@@ -0,0 +1,241 @@
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!');
@@ -0,0 +1,268 @@
1
+ // Tests for HTTP Service abstraction
2
+ // Run with: node tests/httpService.test.js
3
+
4
+ // Simple mock implementation for jest.fn()
5
+ function jest_fn() {
6
+ const calls = [];
7
+ const mockFn = (...args) => {
8
+ calls.push(args);
9
+ return mockFn._returnValue;
10
+ };
11
+ mockFn.mock = { calls };
12
+ mockFn.mockResolvedValue = (value) => {
13
+ mockFn._returnValue = Promise.resolve(value);
14
+ return mockFn;
15
+ };
16
+ return mockFn;
17
+ }
18
+
19
+ // Replace jest with our simple implementation
20
+ const jest = { fn: jest_fn };
21
+
22
+ // Mock fetch for testing
23
+ class MockResponse {
24
+ constructor(data, options = {}) {
25
+ this._data = data;
26
+ this.ok = options.ok !== false;
27
+ this.status = options.status || (this.ok ? 200 : 500);
28
+ this.statusText = options.statusText || (this.ok ? 'OK' : 'Internal Server Error');
29
+ }
30
+
31
+ async json() {
32
+ return this._data;
33
+ }
34
+
35
+ async text() {
36
+ return typeof this._data === 'string' ? this._data : JSON.stringify(this._data);
37
+ }
38
+ }
39
+
40
+ // Mock fetch implementation
41
+ function createMockFetch(responseData, options = {}) {
42
+ return jest.fn().mockResolvedValue(new MockResponse(responseData, options));
43
+ }
44
+
45
+ // HTTP Service abstraction - pure functions, easily testable
46
+ function createHttpService(fetchFn = fetch) {
47
+ return {
48
+ // File operations
49
+ async getFileContent(filePath) {
50
+ const response = await fetchFn(`/api/file-content?path=${encodeURIComponent(filePath)}`);
51
+ if (!response.ok) {
52
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
53
+ }
54
+ return response.text();
55
+ },
56
+
57
+ async saveFile(filePath, content) {
58
+ const response = await fetchFn('/api/save-file', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ path: filePath, content })
62
+ });
63
+ return response.json();
64
+ },
65
+
66
+ async createFile(path, filename) {
67
+ const response = await fetchFn('/api/create-file', {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify({ path, filename })
71
+ });
72
+ return response.json();
73
+ },
74
+
75
+ async deleteFile(path) {
76
+ const response = await fetchFn('/api/delete', {
77
+ method: 'POST',
78
+ headers: { 'Content-Type': 'application/json' },
79
+ body: JSON.stringify({ path })
80
+ });
81
+ return response.json();
82
+ },
83
+
84
+ async renameFile(oldPath, newPath) {
85
+ const response = await fetchFn('/api/rename', {
86
+ method: 'POST',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: JSON.stringify({ oldPath, newPath })
89
+ });
90
+ return response.json();
91
+ },
92
+
93
+ // Folder operations
94
+ async createFolder(path, foldername) {
95
+ const response = await fetchFn('/api/create-folder', {
96
+ method: 'POST',
97
+ headers: { 'Content-Type': 'application/json' },
98
+ body: JSON.stringify({ path, foldername })
99
+ });
100
+ return response.json();
101
+ },
102
+
103
+ // Git operations
104
+ async getGitStatus(currentPath) {
105
+ const response = await fetchFn(`/api/git-status?currentPath=${encodeURIComponent(currentPath)}`);
106
+ return response.json();
107
+ },
108
+
109
+ async getGitDiff(filePath) {
110
+ const response = await fetchFn(`/api/git-diff?path=${encodeURIComponent(filePath)}`);
111
+ return response.json();
112
+ },
113
+
114
+ async commitSelectedFiles(files, message) {
115
+ const response = await fetchFn('/api/git-commit-selected', {
116
+ method: 'POST',
117
+ headers: { 'Content-Type': 'application/json' },
118
+ body: JSON.stringify({ files, message })
119
+ });
120
+ return response.json();
121
+ }
122
+ };
123
+ }
124
+
125
+ // Simple test framework (since we don't have a real test runner)
126
+ function test(name, fn) {
127
+ try {
128
+ fn();
129
+ console.log(`✅ ${name}`);
130
+ } catch (error) {
131
+ console.log(`❌ ${name}: ${error.message}`);
132
+ }
133
+ }
134
+
135
+ function assertEqual(actual, expected, message = '') {
136
+ if (actual !== expected) {
137
+ throw new Error(`Expected "${expected}" but got "${actual}". ${message}`);
138
+ }
139
+ }
140
+
141
+ function assertDeepEqual(actual, expected, message = '') {
142
+ const actualStr = JSON.stringify(actual);
143
+ const expectedStr = JSON.stringify(expected);
144
+ if (actualStr !== expectedStr) {
145
+ throw new Error(`Expected ${expectedStr} but got ${actualStr}. ${message}`);
146
+ }
147
+ }
148
+
149
+ // Test cases
150
+ console.log('🧪 Testing HTTP Service\n');
151
+
152
+ test('gets file content correctly', async () => {
153
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse('file content'));
154
+ const httpService = createHttpService(mockFetch);
155
+
156
+ const content = await httpService.getFileContent('src/app.js');
157
+ assertEqual(content, 'file content');
158
+ assertEqual(mockFetch.mock.calls[0][0], '/api/file-content?path=src%2Fapp.js');
159
+ });
160
+
161
+ test('saves file with correct parameters', async () => {
162
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
163
+ const httpService = createHttpService(mockFetch);
164
+
165
+ const result = await httpService.saveFile('src/app.js', 'new content');
166
+ assertDeepEqual(result, { success: true });
167
+
168
+ const [url, options] = mockFetch.mock.calls[0];
169
+ assertEqual(url, '/api/save-file');
170
+ assertEqual(options.method, 'POST');
171
+ assertDeepEqual(JSON.parse(options.body), { path: 'src/app.js', content: 'new content' });
172
+ });
173
+
174
+ test('creates file with correct parameters', async () => {
175
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
176
+ const httpService = createHttpService(mockFetch);
177
+
178
+ const result = await httpService.createFile('src', 'newfile.js');
179
+ assertDeepEqual(result, { success: true });
180
+
181
+ const [url, options] = mockFetch.mock.calls[0];
182
+ assertEqual(url, '/api/create-file');
183
+ assertDeepEqual(JSON.parse(options.body), { path: 'src', filename: 'newfile.js' });
184
+ });
185
+
186
+ test('deletes file with correct parameters', async () => {
187
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
188
+ const httpService = createHttpService(mockFetch);
189
+
190
+ const result = await httpService.deleteFile('src/oldfile.js');
191
+ assertDeepEqual(result, { success: true });
192
+
193
+ const [url, options] = mockFetch.mock.calls[0];
194
+ assertEqual(url, '/api/delete');
195
+ assertDeepEqual(JSON.parse(options.body), { path: 'src/oldfile.js' });
196
+ });
197
+
198
+ test('renames file with correct parameters', async () => {
199
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
200
+ const httpService = createHttpService(mockFetch);
201
+
202
+ const result = await httpService.renameFile('src/old.js', 'src/new.js');
203
+ assertDeepEqual(result, { success: true });
204
+
205
+ const [url, options] = mockFetch.mock.calls[0];
206
+ assertEqual(url, '/api/rename');
207
+ assertDeepEqual(JSON.parse(options.body), { oldPath: 'src/old.js', newPath: 'src/new.js' });
208
+ });
209
+
210
+ test('creates folder with correct parameters', async () => {
211
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
212
+ const httpService = createHttpService(mockFetch);
213
+
214
+ const result = await httpService.createFolder('src', 'components');
215
+ assertDeepEqual(result, { success: true });
216
+
217
+ const [url, options] = mockFetch.mock.calls[0];
218
+ assertEqual(url, '/api/create-folder');
219
+ assertDeepEqual(JSON.parse(options.body), { path: 'src', foldername: 'components' });
220
+ });
221
+
222
+ test('gets git status with correct parameters', async () => {
223
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ files: [] }));
224
+ const httpService = createHttpService(mockFetch);
225
+
226
+ const result = await httpService.getGitStatus('src');
227
+ assertDeepEqual(result, { files: [] });
228
+
229
+ assertEqual(mockFetch.mock.calls[0][0], '/api/git-status?currentPath=src');
230
+ });
231
+
232
+ test('gets git diff with correct parameters', async () => {
233
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ diff: 'diff content' }));
234
+ const httpService = createHttpService(mockFetch);
235
+
236
+ const result = await httpService.getGitDiff('src/app.js');
237
+ assertDeepEqual(result, { diff: 'diff content' });
238
+
239
+ assertEqual(mockFetch.mock.calls[0][0], '/api/git-diff?path=src%2Fapp.js');
240
+ });
241
+
242
+ test('commits selected files with correct parameters', async () => {
243
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse({ success: true }));
244
+ const httpService = createHttpService(mockFetch);
245
+
246
+ const files = ['src/app.js', 'src/utils.js'];
247
+ const message = 'Fix bugs';
248
+ const result = await httpService.commitSelectedFiles(files, message);
249
+ assertDeepEqual(result, { success: true });
250
+
251
+ const [url, options] = mockFetch.mock.calls[0];
252
+ assertEqual(url, '/api/git-commit-selected');
253
+ assertDeepEqual(JSON.parse(options.body), { files, message });
254
+ });
255
+
256
+ test('handles HTTP errors correctly', async () => {
257
+ const mockFetch = jest.fn().mockResolvedValue(new MockResponse('Not found', { ok: false, status: 404, statusText: 'Not Found' }));
258
+ const httpService = createHttpService(mockFetch);
259
+
260
+ try {
261
+ await httpService.getFileContent('nonexistent.js');
262
+ throw new Error('Should have thrown an error');
263
+ } catch (error) {
264
+ assertEqual(error.message, 'HTTP 404: Not Found');
265
+ }
266
+ });
267
+
268
+ console.log('\n🎉 HTTP Service tests complete!');
@@ -0,0 +1,145 @@
1
+ // Simple unit tests for language detection
2
+ // Run with: node tests/languageDetection.test.js
3
+
4
+ // Import the function (in a real setup, you'd use proper modules)
5
+ // For now, we'll copy the function to test it standalone
6
+
7
+ function getLanguageFromExtension(filename) {
8
+ const ext = filename.split('.').pop().toLowerCase();
9
+ const languageMap = {
10
+ // JavaScript family
11
+ 'js': 'javascript',
12
+ 'mjs': 'javascript',
13
+ 'jsx': 'javascript',
14
+ 'ts': 'typescript',
15
+ 'tsx': 'typescript',
16
+
17
+ // Web languages
18
+ 'html': 'html',
19
+ 'htm': 'html',
20
+ 'css': 'css',
21
+ 'scss': 'scss',
22
+ 'sass': 'sass',
23
+ 'less': 'less',
24
+
25
+ // Data formats
26
+ 'json': 'json',
27
+ 'xml': 'xml',
28
+ 'yaml': 'yaml',
29
+ 'yml': 'yaml',
30
+
31
+ // Programming languages
32
+ 'py': 'python',
33
+ 'java': 'java',
34
+ 'go': 'go',
35
+ 'rs': 'rust',
36
+ 'php': 'php',
37
+ 'rb': 'ruby',
38
+ 'swift': 'swift',
39
+ 'kt': 'kotlin',
40
+ 'dart': 'dart',
41
+
42
+ // Systems languages
43
+ 'c': 'c',
44
+ 'cpp': 'cpp',
45
+ 'cc': 'cpp',
46
+ 'cxx': 'cpp',
47
+ 'h': 'c',
48
+ 'hpp': 'cpp',
49
+
50
+ // Shell and scripts
51
+ 'sh': 'shell',
52
+ 'bash': 'shell',
53
+ 'zsh': 'shell',
54
+ 'fish': 'shell',
55
+ 'ps1': 'powershell',
56
+
57
+ // Other languages
58
+ 'sql': 'sql',
59
+ 'r': 'r',
60
+ 'scala': 'scala',
61
+ 'clj': 'clojure',
62
+ 'lua': 'lua',
63
+ 'pl': 'perl',
64
+ 'groovy': 'groovy',
65
+
66
+ // Config and text
67
+ 'md': 'markdown',
68
+ 'txt': 'plaintext',
69
+ 'log': 'plaintext'
70
+ };
71
+
72
+ // Special filename handling
73
+ const basename = filename.toLowerCase();
74
+ if (basename === 'dockerfile' || basename.startsWith('dockerfile.')) return 'dockerfile';
75
+ if (basename === 'makefile') return 'makefile';
76
+ if (basename.startsWith('.env')) return 'dotenv';
77
+ if (basename === 'package.json' || basename === 'composer.json') return 'json';
78
+
79
+ return languageMap[ext] || 'plaintext';
80
+ }
81
+
82
+ // Simple test framework
83
+ function test(name, fn) {
84
+ try {
85
+ fn();
86
+ console.log(`✅ ${name}`);
87
+ } catch (error) {
88
+ console.log(`❌ ${name}: ${error.message}`);
89
+ }
90
+ }
91
+
92
+ function assertEqual(actual, expected, message = '') {
93
+ if (actual !== expected) {
94
+ throw new Error(`Expected "${expected}" but got "${actual}". ${message}`);
95
+ }
96
+ }
97
+
98
+ // Test cases
99
+ console.log('🧪 Testing Language Detection Function\n');
100
+
101
+ test('detects JavaScript files', () => {
102
+ assertEqual(getLanguageFromExtension('app.js'), 'javascript');
103
+ assertEqual(getLanguageFromExtension('module.mjs'), 'javascript');
104
+ assertEqual(getLanguageFromExtension('Component.jsx'), 'javascript');
105
+ });
106
+
107
+ test('detects TypeScript files', () => {
108
+ assertEqual(getLanguageFromExtension('main.ts'), 'typescript');
109
+ assertEqual(getLanguageFromExtension('Component.tsx'), 'typescript');
110
+ });
111
+
112
+ test('detects web languages', () => {
113
+ assertEqual(getLanguageFromExtension('index.html'), 'html');
114
+ assertEqual(getLanguageFromExtension('styles.css'), 'css');
115
+ assertEqual(getLanguageFromExtension('styles.scss'), 'scss');
116
+ });
117
+
118
+ test('detects programming languages', () => {
119
+ assertEqual(getLanguageFromExtension('script.py'), 'python');
120
+ assertEqual(getLanguageFromExtension('Main.java'), 'java');
121
+ assertEqual(getLanguageFromExtension('main.go'), 'go');
122
+ assertEqual(getLanguageFromExtension('lib.rs'), 'rust');
123
+ });
124
+
125
+ test('detects special filenames', () => {
126
+ assertEqual(getLanguageFromExtension('Dockerfile'), 'dockerfile');
127
+ assertEqual(getLanguageFromExtension('dockerfile.prod'), 'dockerfile');
128
+ assertEqual(getLanguageFromExtension('Makefile'), 'makefile');
129
+ assertEqual(getLanguageFromExtension('.env'), 'dotenv');
130
+ assertEqual(getLanguageFromExtension('.env.local'), 'dotenv');
131
+ assertEqual(getLanguageFromExtension('package.json'), 'json');
132
+ });
133
+
134
+ test('defaults to plaintext for unknown extensions', () => {
135
+ assertEqual(getLanguageFromExtension('file.unknown'), 'plaintext');
136
+ assertEqual(getLanguageFromExtension('README'), 'plaintext');
137
+ assertEqual(getLanguageFromExtension('file.xyz'), 'plaintext');
138
+ });
139
+
140
+ test('handles files without extensions', () => {
141
+ assertEqual(getLanguageFromExtension('README'), 'plaintext');
142
+ assertEqual(getLanguageFromExtension('LICENSE'), 'plaintext');
143
+ });
144
+
145
+ console.log('\n🎉 Language detection tests complete!');