@amirdaraee/namewise 0.3.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 (105) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +82 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +61 -0
  3. package/.github/workflows/auto-release.yml +78 -0
  4. package/.github/workflows/ci.yml +78 -0
  5. package/.github/workflows/publish.yml +43 -0
  6. package/.github/workflows/test.yml +37 -0
  7. package/CHANGELOG.md +128 -0
  8. package/LICENSE +21 -0
  9. package/README.md +251 -0
  10. package/dist/cli/commands.d.ts +3 -0
  11. package/dist/cli/commands.d.ts.map +1 -0
  12. package/dist/cli/commands.js +19 -0
  13. package/dist/cli/commands.js.map +1 -0
  14. package/dist/cli/rename.d.ts +2 -0
  15. package/dist/cli/rename.d.ts.map +1 -0
  16. package/dist/cli/rename.js +136 -0
  17. package/dist/cli/rename.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +13 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/parsers/excel-parser.d.ts +6 -0
  23. package/dist/parsers/excel-parser.d.ts.map +1 -0
  24. package/dist/parsers/excel-parser.js +42 -0
  25. package/dist/parsers/excel-parser.js.map +1 -0
  26. package/dist/parsers/factory.d.ts +7 -0
  27. package/dist/parsers/factory.d.ts.map +1 -0
  28. package/dist/parsers/factory.js +29 -0
  29. package/dist/parsers/factory.js.map +1 -0
  30. package/dist/parsers/pdf-parser.d.ts +7 -0
  31. package/dist/parsers/pdf-parser.d.ts.map +1 -0
  32. package/dist/parsers/pdf-parser.js +67 -0
  33. package/dist/parsers/pdf-parser.js.map +1 -0
  34. package/dist/parsers/text-parser.d.ts +6 -0
  35. package/dist/parsers/text-parser.d.ts.map +1 -0
  36. package/dist/parsers/text-parser.js +39 -0
  37. package/dist/parsers/text-parser.js.map +1 -0
  38. package/dist/parsers/word-parser.d.ts +6 -0
  39. package/dist/parsers/word-parser.d.ts.map +1 -0
  40. package/dist/parsers/word-parser.js +44 -0
  41. package/dist/parsers/word-parser.js.map +1 -0
  42. package/dist/services/ai-factory.d.ts +5 -0
  43. package/dist/services/ai-factory.d.ts.map +1 -0
  44. package/dist/services/ai-factory.js +15 -0
  45. package/dist/services/ai-factory.js.map +1 -0
  46. package/dist/services/claude-service.d.ts +9 -0
  47. package/dist/services/claude-service.d.ts.map +1 -0
  48. package/dist/services/claude-service.js +113 -0
  49. package/dist/services/claude-service.js.map +1 -0
  50. package/dist/services/file-renamer.d.ts +12 -0
  51. package/dist/services/file-renamer.d.ts.map +1 -0
  52. package/dist/services/file-renamer.js +99 -0
  53. package/dist/services/file-renamer.js.map +1 -0
  54. package/dist/services/openai-service.d.ts +9 -0
  55. package/dist/services/openai-service.d.ts.map +1 -0
  56. package/dist/services/openai-service.js +112 -0
  57. package/dist/services/openai-service.js.map +1 -0
  58. package/dist/types/index.d.ts +61 -0
  59. package/dist/types/index.d.ts.map +1 -0
  60. package/dist/types/index.js +2 -0
  61. package/dist/types/index.js.map +1 -0
  62. package/dist/utils/file-templates.d.ts +18 -0
  63. package/dist/utils/file-templates.d.ts.map +1 -0
  64. package/dist/utils/file-templates.js +232 -0
  65. package/dist/utils/file-templates.js.map +1 -0
  66. package/dist/utils/naming-conventions.d.ts +4 -0
  67. package/dist/utils/naming-conventions.d.ts.map +1 -0
  68. package/dist/utils/naming-conventions.js +55 -0
  69. package/dist/utils/naming-conventions.js.map +1 -0
  70. package/package.json +75 -0
  71. package/src/cli/commands.ts +20 -0
  72. package/src/cli/rename.ts +157 -0
  73. package/src/index.ts +17 -0
  74. package/src/parsers/excel-parser.ts +49 -0
  75. package/src/parsers/factory.ts +34 -0
  76. package/src/parsers/pdf-parser.ts +78 -0
  77. package/src/parsers/text-parser.ts +43 -0
  78. package/src/parsers/word-parser.ts +50 -0
  79. package/src/services/ai-factory.ts +16 -0
  80. package/src/services/claude-service.ts +114 -0
  81. package/src/services/file-renamer.ts +123 -0
  82. package/src/services/openai-service.ts +113 -0
  83. package/src/types/index.ts +71 -0
  84. package/src/types/pdf-extraction.d.ts +7 -0
  85. package/src/utils/file-templates.ts +275 -0
  86. package/src/utils/naming-conventions.ts +67 -0
  87. package/tests/data/empty-file.txt +0 -0
  88. package/tests/data/sample-markdown.md +9 -0
  89. package/tests/data/sample-pdf.pdf +0 -0
  90. package/tests/data/sample-text.txt +25 -0
  91. package/tests/integration/end-to-end.test.ts +209 -0
  92. package/tests/integration/workflow.test.ts +336 -0
  93. package/tests/mocks/mock-ai-service.ts +58 -0
  94. package/tests/unit/cli/commands.test.ts +163 -0
  95. package/tests/unit/parsers/factory.test.ts +100 -0
  96. package/tests/unit/parsers/pdf-parser.test.ts +63 -0
  97. package/tests/unit/parsers/text-parser.test.ts +85 -0
  98. package/tests/unit/services/ai-factory.test.ts +37 -0
  99. package/tests/unit/services/claude-service.test.ts +188 -0
  100. package/tests/unit/services/file-renamer.test.ts +299 -0
  101. package/tests/unit/services/openai-service.test.ts +196 -0
  102. package/tests/unit/utils/file-templates.test.ts +199 -0
  103. package/tests/unit/utils/naming-conventions.test.ts +88 -0
  104. package/tsconfig.json +20 -0
  105. package/vitest.config.ts +30 -0
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { OpenAIService } from '../../../src/services/openai-service.js';
3
+
4
+ // Mock the OpenAI SDK
5
+ vi.mock('openai', () => {
6
+ const MockOpenAI = vi.fn().mockImplementation(() => ({
7
+ chat: {
8
+ completions: {
9
+ create: vi.fn()
10
+ }
11
+ }
12
+ }));
13
+ return {
14
+ default: MockOpenAI
15
+ };
16
+ });
17
+
18
+ describe('OpenAIService', () => {
19
+ let service: OpenAIService;
20
+ let mockClient: any;
21
+
22
+ beforeEach(() => {
23
+ service = new OpenAIService('test-api-key');
24
+ mockClient = (service as any).client;
25
+ });
26
+
27
+ describe('Basic Properties', () => {
28
+ it('should have correct name', () => {
29
+ expect(service.name).toBe('OpenAI');
30
+ });
31
+
32
+ it('should initialize with API key', () => {
33
+ expect(service).toBeDefined();
34
+ });
35
+ });
36
+
37
+ describe('generateFileName() with different naming conventions', () => {
38
+ const sampleContent = 'This is a user manual for software installation and configuration.';
39
+ const originalName = 'manual.docx';
40
+
41
+ beforeEach(() => {
42
+ mockClient.chat.completions.create.mockResolvedValue({
43
+ choices: [
44
+ {
45
+ message: {
46
+ content: 'user manual software installation configuration'
47
+ }
48
+ }
49
+ ]
50
+ });
51
+ });
52
+
53
+ it('should generate filename with kebab-case convention (default)', async () => {
54
+ const result = await service.generateFileName(sampleContent, originalName);
55
+
56
+ expect(mockClient.chat.completions.create).toHaveBeenCalledWith(
57
+ expect.objectContaining({
58
+ model: 'gpt-3.5-turbo',
59
+ messages: [expect.objectContaining({
60
+ role: 'user',
61
+ content: expect.stringContaining('Use lowercase with hyphens between words')
62
+ })],
63
+ temperature: 0.3
64
+ })
65
+ );
66
+
67
+ expect(result).toBe('user-manual-software-installation-configuration');
68
+ });
69
+
70
+ it('should generate filename with snake_case convention', async () => {
71
+ const result = await service.generateFileName(sampleContent, originalName, 'snake_case');
72
+
73
+ expect(mockClient.chat.completions.create).toHaveBeenCalledWith(
74
+ expect.objectContaining({
75
+ messages: [expect.objectContaining({
76
+ content: expect.stringContaining('Use lowercase with underscores between words')
77
+ })]
78
+ })
79
+ );
80
+
81
+ expect(result).toBe('user_manual_software_installation_configuration');
82
+ });
83
+
84
+ it('should generate filename with camelCase convention', async () => {
85
+ const result = await service.generateFileName(sampleContent, originalName, 'camelCase');
86
+
87
+ expect(mockClient.chat.completions.create).toHaveBeenCalledWith(
88
+ expect.objectContaining({
89
+ messages: [expect.objectContaining({
90
+ content: expect.stringContaining('Use camelCase format starting with lowercase')
91
+ })]
92
+ })
93
+ );
94
+
95
+ expect(result).toBe('userManualSoftwareInstallationConfiguration');
96
+ });
97
+
98
+ it('should generate filename with UPPERCASE convention', async () => {
99
+ const result = await service.generateFileName(sampleContent, originalName, 'UPPERCASE');
100
+
101
+ expect(result).toBe('USERMANUALSOFTWAREINSTALLATIONCONFIGURATION');
102
+ });
103
+
104
+ it('should include original filename and content in prompt', async () => {
105
+ await service.generateFileName(sampleContent, originalName, 'kebab-case');
106
+
107
+ const call = mockClient.chat.completions.create.mock.calls[0][0];
108
+ expect(call.messages[0].content).toContain(sampleContent.substring(0, 2000));
109
+ expect(call.messages[0].content).toContain('Document content (first 2000 characters)');
110
+ });
111
+
112
+ it('should use correct OpenAI parameters', async () => {
113
+ await service.generateFileName(sampleContent, originalName);
114
+
115
+ expect(mockClient.chat.completions.create).toHaveBeenCalledWith(
116
+ expect.objectContaining({
117
+ model: 'gpt-3.5-turbo',
118
+ max_tokens: 100,
119
+ temperature: 0.3
120
+ })
121
+ );
122
+ });
123
+ });
124
+
125
+ describe('Error Handling', () => {
126
+ it('should handle API errors gracefully', async () => {
127
+ mockClient.chat.completions.create.mockRejectedValue(new Error('OpenAI API Error'));
128
+
129
+ await expect(service.generateFileName('content', 'file.txt')).rejects.toThrow(
130
+ 'Failed to generate filename with OpenAI: OpenAI API Error'
131
+ );
132
+ });
133
+
134
+ it('should handle empty choices response', async () => {
135
+ mockClient.chat.completions.create.mockResolvedValue({
136
+ choices: []
137
+ });
138
+
139
+ const result = await service.generateFileName('content', 'file.txt');
140
+ expect(result).toBe('untitled-document');
141
+ });
142
+
143
+ it('should handle missing message content', async () => {
144
+ mockClient.chat.completions.create.mockResolvedValue({
145
+ choices: [
146
+ {
147
+ message: {
148
+ content: null
149
+ }
150
+ }
151
+ ]
152
+ });
153
+
154
+ const result = await service.generateFileName('content', 'file.txt');
155
+ expect(result).toBe('untitled-document');
156
+ });
157
+
158
+ it('should handle undefined choices', async () => {
159
+ mockClient.chat.completions.create.mockResolvedValue({
160
+ choices: [undefined]
161
+ });
162
+
163
+ const result = await service.generateFileName('content', 'file.txt');
164
+ expect(result).toBe('untitled-document');
165
+ });
166
+ });
167
+
168
+ describe('Filename Sanitization', () => {
169
+ beforeEach(() => {
170
+ mockClient.chat.completions.create.mockResolvedValue({
171
+ choices: [
172
+ {
173
+ message: {
174
+ content: 'Test@Document#With$Special%Characters.xlsx'
175
+ }
176
+ }
177
+ ]
178
+ });
179
+ });
180
+
181
+ it('should sanitize special characters and apply naming convention', async () => {
182
+ const result = await service.generateFileName('content', 'original.txt', 'kebab-case');
183
+ expect(result).toBe('testdocumentwithspecialcharacters');
184
+ });
185
+
186
+ it('should handle different conventions for sanitized input', async () => {
187
+ const kebabResult = await service.generateFileName('content', 'file.txt', 'kebab-case');
188
+ const snakeResult = await service.generateFileName('content', 'file.txt', 'snake_case');
189
+ const camelResult = await service.generateFileName('content', 'file.txt', 'camelCase');
190
+
191
+ expect(kebabResult).toBe('testdocumentwithspecialcharacters');
192
+ expect(snakeResult).toBe('testdocumentwithspecialcharacters');
193
+ expect(camelResult).toBe('testdocumentwithspecialcharacters');
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,199 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ categorizeFile,
4
+ applyTemplate,
5
+ getTemplateInstructions,
6
+ FILE_TEMPLATES,
7
+ FileCategory
8
+ } from '../../../src/utils/file-templates.js';
9
+
10
+ describe('File Templates', () => {
11
+ describe('categorizeFile()', () => {
12
+ it('should categorize document files', () => {
13
+ expect(categorizeFile('/path/contract.pdf')).toBe('document');
14
+ expect(categorizeFile('/path/license.docx')).toBe('document');
15
+ expect(categorizeFile('/path/certificate.txt')).toBe('document');
16
+ });
17
+
18
+ it('should categorize document files by content', () => {
19
+ expect(categorizeFile('/path/file.pdf', 'This is a work contract agreement')).toBe('document');
20
+ expect(categorizeFile('/path/file.pdf', 'License application form')).toBe('document');
21
+ expect(categorizeFile('/path/file.pdf', 'Invoice for services rendered')).toBe('document');
22
+ });
23
+
24
+ it('should categorize movie files', () => {
25
+ expect(categorizeFile('/path/movie.mp4')).toBe('movie');
26
+ expect(categorizeFile('/path/film.mkv')).toBe('movie');
27
+ expect(categorizeFile('/path/video.avi')).toBe('movie');
28
+ });
29
+
30
+ it('should categorize series files', () => {
31
+ expect(categorizeFile('/path/show.s01e01.mkv')).toBe('series');
32
+ expect(categorizeFile('/path/series.season.1.mp4')).toBe('series');
33
+ expect(categorizeFile('/path/tv.episode.avi')).toBe('series');
34
+ });
35
+
36
+ it('should categorize music files', () => {
37
+ expect(categorizeFile('/path/song.mp3')).toBe('music');
38
+ expect(categorizeFile('/path/audio.flac')).toBe('music');
39
+ expect(categorizeFile('/path/track.wav')).toBe('music');
40
+ });
41
+
42
+ it('should categorize photo files', () => {
43
+ expect(categorizeFile('/path/image.jpg')).toBe('photo');
44
+ expect(categorizeFile('/path/picture.png')).toBe('photo');
45
+ expect(categorizeFile('/path/photo.heic')).toBe('photo');
46
+ });
47
+
48
+ it('should categorize book files', () => {
49
+ expect(categorizeFile('/path/novel.epub')).toBe('book');
50
+ expect(categorizeFile('/path/book.mobi')).toBe('book');
51
+ expect(categorizeFile('/path/ebook.azw')).toBe('book');
52
+ });
53
+
54
+ it('should default to general for unknown types', () => {
55
+ expect(categorizeFile('/path/unknown.xyz')).toBe('general');
56
+ expect(categorizeFile('/path/file')).toBe('general');
57
+ });
58
+
59
+ it('should prioritize series over movie for video files with series keywords', () => {
60
+ expect(categorizeFile('/path/breaking-bad.s01e01.mp4')).toBe('series');
61
+ expect(categorizeFile('/path/movie.mkv', 'This is season 1 episode 1')).toBe('series');
62
+ });
63
+ });
64
+
65
+ describe('applyTemplate()', () => {
66
+ it('should apply document template with personal name and date', () => {
67
+ const result = applyTemplate(
68
+ 'driving-license',
69
+ 'document',
70
+ { category: 'document', personalName: 'amirhossein', dateFormat: 'YYYYMMDD' },
71
+ 'kebab-case'
72
+ );
73
+
74
+ // Should match pattern: {content}-{personalName}-{date}
75
+ expect(result).toMatch(/^driving-license-amirhossein-\d{8}$/);
76
+ });
77
+
78
+ it('should apply movie template with year', () => {
79
+ const result = applyTemplate(
80
+ 'the-dark-knight',
81
+ 'movie',
82
+ { category: 'movie', dateFormat: 'YYYY' },
83
+ 'kebab-case'
84
+ );
85
+
86
+ // Movies don't use personal names in their template pattern
87
+ expect(result).toBe('the-dark-knight');
88
+ });
89
+
90
+ it('should apply different date formats', () => {
91
+ const baseOptions = { category: 'document' as FileCategory, personalName: 'john' };
92
+
93
+ const yyyymmdd = applyTemplate('contract', 'document', { ...baseOptions, dateFormat: 'YYYYMMDD' }, 'kebab-case');
94
+ const yyyymmdd2 = applyTemplate('contract', 'document', { ...baseOptions, dateFormat: 'YYYY-MM-DD' }, 'kebab-case');
95
+ const yyyy = applyTemplate('contract', 'document', { ...baseOptions, dateFormat: 'YYYY' }, 'kebab-case');
96
+
97
+ expect(yyyymmdd).toMatch(/^contract-john-\d{8}$/);
98
+ expect(yyyymmdd2).toMatch(/^contract-john-\d{4}-\d{2}-\d{2}$/);
99
+ expect(yyyy).toMatch(/^contract-john-\d{4}$/);
100
+ });
101
+
102
+ it('should handle no date format', () => {
103
+ const result = applyTemplate(
104
+ 'document',
105
+ 'document',
106
+ { category: 'document', personalName: 'jane', dateFormat: 'none' },
107
+ 'kebab-case'
108
+ );
109
+
110
+ expect(result).toBe('document-jane');
111
+ });
112
+
113
+ it('should handle missing personal name', () => {
114
+ const result = applyTemplate(
115
+ 'report',
116
+ 'document',
117
+ { category: 'document', dateFormat: 'YYYY' },
118
+ 'kebab-case'
119
+ );
120
+
121
+ expect(result).toMatch(/^report-\d{4}$/);
122
+ });
123
+
124
+ it('should apply naming conventions correctly', () => {
125
+ const baseOptions = { category: 'document' as FileCategory, personalName: 'test-user', dateFormat: 'none' as const };
126
+
127
+ const kebab = applyTemplate('My Document', 'document', baseOptions, 'kebab-case');
128
+ const snake = applyTemplate('My Document', 'document', baseOptions, 'snake_case');
129
+ const camel = applyTemplate('My Document', 'document', baseOptions, 'camelCase');
130
+
131
+ expect(kebab).toBe('my-document-test-user');
132
+ expect(snake).toBe('my_document_test_user');
133
+ expect(camel).toBe('myDocumentTestUser');
134
+ });
135
+
136
+ it('should clean up multiple separators', () => {
137
+ const result = applyTemplate(
138
+ 'test--document',
139
+ 'document',
140
+ { category: 'document', personalName: 'user', dateFormat: 'none' },
141
+ 'kebab-case'
142
+ );
143
+
144
+ expect(result).toBe('test-document-user');
145
+ });
146
+
147
+ it('should handle general category', () => {
148
+ const result = applyTemplate(
149
+ 'meeting-notes',
150
+ 'general',
151
+ { category: 'general', personalName: 'admin', dateFormat: 'YYYY' },
152
+ 'kebab-case'
153
+ );
154
+
155
+ expect(result).toBe('meeting-notes');
156
+ });
157
+ });
158
+
159
+ describe('getTemplateInstructions()', () => {
160
+ it('should return instructions for each category', () => {
161
+ const categories: FileCategory[] = ['document', 'movie', 'music', 'series', 'photo', 'book', 'general'];
162
+
163
+ categories.forEach(category => {
164
+ const instructions = getTemplateInstructions(category);
165
+ expect(instructions).toContain(category);
166
+ expect(instructions.length).toBeGreaterThan(10);
167
+ });
168
+ });
169
+
170
+ it('should include examples in instructions', () => {
171
+ const documentInstructions = getTemplateInstructions('document');
172
+ expect(documentInstructions).toContain('driving-license-amirhossein');
173
+
174
+ const movieInstructions = getTemplateInstructions('movie');
175
+ expect(movieInstructions).toContain('the-dark-knight-2008');
176
+ });
177
+ });
178
+
179
+ describe('FILE_TEMPLATES', () => {
180
+ it('should have all required template categories', () => {
181
+ const expectedCategories: FileCategory[] = ['document', 'movie', 'music', 'series', 'photo', 'book', 'general'];
182
+
183
+ expectedCategories.forEach(category => {
184
+ expect(FILE_TEMPLATES[category]).toBeDefined();
185
+ expect(FILE_TEMPLATES[category].category).toBe(category);
186
+ expect(FILE_TEMPLATES[category].pattern).toBeDefined();
187
+ expect(FILE_TEMPLATES[category].description).toBeDefined();
188
+ expect(FILE_TEMPLATES[category].examples.length).toBeGreaterThan(0);
189
+ });
190
+ });
191
+
192
+ it('should have valid patterns', () => {
193
+ Object.values(FILE_TEMPLATES).forEach(template => {
194
+ expect(template.pattern).toContain('{content}');
195
+ expect(template.examples.length).toBeGreaterThan(2);
196
+ });
197
+ });
198
+ });
199
+ });
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { applyNamingConvention, getNamingInstructions, NamingConvention } from '../../../src/utils/naming-conventions.js';
3
+
4
+ describe('Naming Conventions', () => {
5
+ describe('applyNamingConvention()', () => {
6
+ const testText = 'Project Requirements Document 2024';
7
+
8
+ it('should apply kebab-case convention', () => {
9
+ const result = applyNamingConvention(testText, 'kebab-case');
10
+ expect(result).toBe('project-requirements-document-2024');
11
+ });
12
+
13
+ it('should apply snake_case convention', () => {
14
+ const result = applyNamingConvention(testText, 'snake_case');
15
+ expect(result).toBe('project_requirements_document_2024');
16
+ });
17
+
18
+ it('should apply camelCase convention', () => {
19
+ const result = applyNamingConvention(testText, 'camelCase');
20
+ expect(result).toBe('projectRequirementsDocument2024');
21
+ });
22
+
23
+ it('should apply PascalCase convention', () => {
24
+ const result = applyNamingConvention(testText, 'PascalCase');
25
+ expect(result).toBe('ProjectRequirementsDocument2024');
26
+ });
27
+
28
+ it('should apply lowercase convention', () => {
29
+ const result = applyNamingConvention(testText, 'lowercase');
30
+ expect(result).toBe('projectrequirementsdocument2024');
31
+ });
32
+
33
+ it('should apply UPPERCASE convention', () => {
34
+ const result = applyNamingConvention(testText, 'UPPERCASE');
35
+ expect(result).toBe('PROJECTREQUIREMENTSDOCUMENT2024');
36
+ });
37
+
38
+ it('should handle text with special characters', () => {
39
+ const textWithSpecialChars = 'User@Guide & Manual (v2.1)';
40
+
41
+ expect(applyNamingConvention(textWithSpecialChars, 'kebab-case')).toBe('userguide-manual-v21');
42
+ expect(applyNamingConvention(textWithSpecialChars, 'snake_case')).toBe('userguide_manual_v21');
43
+ expect(applyNamingConvention(textWithSpecialChars, 'camelCase')).toBe('userguideManualV21');
44
+ expect(applyNamingConvention(textWithSpecialChars, 'PascalCase')).toBe('UserguideManualV21');
45
+ });
46
+
47
+ it('should handle text with existing hyphens and underscores', () => {
48
+ const textWithSeparators = 'my-file_name document';
49
+
50
+ expect(applyNamingConvention(textWithSeparators, 'kebab-case')).toBe('my-file-name-document');
51
+ expect(applyNamingConvention(textWithSeparators, 'snake_case')).toBe('my_file_name_document');
52
+ expect(applyNamingConvention(textWithSeparators, 'camelCase')).toBe('myFileNameDocument');
53
+ });
54
+
55
+ it('should handle empty and whitespace-only strings', () => {
56
+ expect(applyNamingConvention('', 'kebab-case')).toBe('');
57
+ expect(applyNamingConvention(' ', 'kebab-case')).toBe('');
58
+ });
59
+
60
+ it('should normalize multiple spaces', () => {
61
+ const textWithSpaces = 'Meeting Notes From Today';
62
+ expect(applyNamingConvention(textWithSpaces, 'kebab-case')).toBe('meeting-notes-from-today');
63
+ });
64
+
65
+ it('should default to kebab-case for unknown convention', () => {
66
+ const result = applyNamingConvention(testText, 'unknown' as NamingConvention);
67
+ expect(result).toBe('project-requirements-document-2024');
68
+ });
69
+ });
70
+
71
+ describe('getNamingInstructions()', () => {
72
+ it('should return correct instructions for each convention', () => {
73
+ expect(getNamingInstructions('kebab-case')).toContain('lowercase with hyphens');
74
+ expect(getNamingInstructions('snake_case')).toContain('lowercase with underscores');
75
+ expect(getNamingInstructions('camelCase')).toContain('camelCase format starting with lowercase');
76
+ expect(getNamingInstructions('PascalCase')).toContain('PascalCase format starting with uppercase');
77
+ expect(getNamingInstructions('lowercase')).toContain('single lowercase word');
78
+ expect(getNamingInstructions('UPPERCASE')).toContain('single uppercase word');
79
+ });
80
+
81
+ it('should include examples in instructions', () => {
82
+ expect(getNamingInstructions('kebab-case')).toContain('meeting-notes-2024');
83
+ expect(getNamingInstructions('snake_case')).toContain('meeting_notes_2024');
84
+ expect(getNamingInstructions('camelCase')).toContain('meetingNotes2024');
85
+ expect(getNamingInstructions('PascalCase')).toContain('MeetingNotes2024');
86
+ });
87
+ });
88
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import path from 'path';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ environment: 'node',
8
+ include: ['tests/**/*.test.{ts,js}'],
9
+ exclude: ['node_modules', 'dist'],
10
+ coverage: {
11
+ provider: 'v8',
12
+ include: ['src/**/*.ts'],
13
+ exclude: ['src/types/**', 'src/**/*.d.ts'],
14
+ reporter: ['text', 'json', 'html'],
15
+ thresholds: {
16
+ global: {
17
+ branches: 80,
18
+ functions: 80,
19
+ lines: 80,
20
+ statements: 80
21
+ }
22
+ }
23
+ }
24
+ },
25
+ resolve: {
26
+ alias: {
27
+ '@': path.resolve(__dirname, 'src')
28
+ }
29
+ }
30
+ });