tlc-claude-code 1.7.0 → 1.8.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.
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Cleanup Dry-Run Mode Tests
3
+ *
4
+ * Preview what /tlc:cleanup would change without making modifications.
5
+ */
6
+ import { describe, it, expect, vi } from 'vitest';
7
+
8
+ const {
9
+ planCleanup,
10
+ listFilesToMove,
11
+ listHardcodedUrls,
12
+ listInterfacesToExtract,
13
+ listFunctionsNeedingJsDoc,
14
+ planCommitMessages,
15
+ } = require('./cleanup-dry-run.js');
16
+
17
+ describe('Cleanup Dry-Run Mode', () => {
18
+ describe('listFilesToMove', () => {
19
+ it('lists files that would be moved from flat folders', async () => {
20
+ const mockGlob = vi.fn()
21
+ .mockResolvedValueOnce(['src/services/userService.js', 'src/services/orderService.js'])
22
+ .mockResolvedValueOnce([]) // interfaces
23
+ .mockResolvedValueOnce([]); // controllers
24
+
25
+ const result = await listFilesToMove('/project', { glob: mockGlob });
26
+ expect(result).toHaveLength(2);
27
+ expect(result[0]).toMatchObject({
28
+ source: expect.stringContaining('services'),
29
+ destination: expect.any(String),
30
+ reason: expect.any(String),
31
+ });
32
+ });
33
+
34
+ it('returns empty for clean project', async () => {
35
+ const mockGlob = vi.fn().mockResolvedValue([]);
36
+
37
+ const result = await listFilesToMove('/project', { glob: mockGlob });
38
+ expect(result).toHaveLength(0);
39
+ });
40
+ });
41
+
42
+ describe('listHardcodedUrls', () => {
43
+ it('lists hardcoded URLs that would be extracted', async () => {
44
+ const mockGlob = vi.fn().mockResolvedValue(['src/api.js']);
45
+ const mockReadFile = vi.fn().mockResolvedValue(
46
+ 'const api = "http://localhost:3000/api";\nconst port = 8080;'
47
+ );
48
+
49
+ const result = await listHardcodedUrls('/project', {
50
+ glob: mockGlob,
51
+ readFile: mockReadFile,
52
+ });
53
+
54
+ expect(result.length).toBeGreaterThan(0);
55
+ expect(result[0]).toMatchObject({
56
+ file: expect.any(String),
57
+ value: expect.any(String),
58
+ suggestedEnvVar: expect.any(String),
59
+ });
60
+ });
61
+ });
62
+
63
+ describe('listInterfacesToExtract', () => {
64
+ it('lists interfaces that would be extracted', async () => {
65
+ const mockGlob = vi.fn().mockResolvedValue(['src/user/user.service.ts']);
66
+ const mockReadFile = vi.fn().mockResolvedValue(
67
+ 'interface UserDTO {\n id: number;\n name: string;\n}\n\nexport class UserService {}'
68
+ );
69
+
70
+ const result = await listInterfacesToExtract('/project', {
71
+ glob: mockGlob,
72
+ readFile: mockReadFile,
73
+ });
74
+
75
+ expect(result).toHaveLength(1);
76
+ expect(result[0]).toMatchObject({
77
+ file: expect.any(String),
78
+ interfaceName: 'UserDTO',
79
+ targetPath: expect.any(String),
80
+ });
81
+ });
82
+ });
83
+
84
+ describe('listFunctionsNeedingJsDoc', () => {
85
+ it('lists functions needing JSDoc', async () => {
86
+ const mockGlob = vi.fn().mockResolvedValue(['src/utils.js']);
87
+ const mockReadFile = vi.fn().mockResolvedValue(
88
+ 'export function calculateTotal(items, tax) {\n return items.reduce((s, i) => s + i.price, 0) * (1 + tax);\n}'
89
+ );
90
+
91
+ const result = await listFunctionsNeedingJsDoc('/project', {
92
+ glob: mockGlob,
93
+ readFile: mockReadFile,
94
+ });
95
+
96
+ expect(result).toHaveLength(1);
97
+ expect(result[0]).toMatchObject({
98
+ file: expect.any(String),
99
+ functionName: 'calculateTotal',
100
+ });
101
+ });
102
+ });
103
+
104
+ describe('planCommitMessages', () => {
105
+ it('shows planned commit messages', () => {
106
+ const plan = {
107
+ filesToMove: [{ source: 'src/services/user.js', destination: 'src/user/user.js' }],
108
+ hardcodedUrls: [{ file: 'src/api.js', value: 'http://localhost:3000' }],
109
+ interfacesToExtract: [{ file: 'src/user.service.ts', interfaceName: 'UserDTO' }],
110
+ functionsNeedingJsDoc: [{ file: 'src/utils.js', functionName: 'calculate' }],
111
+ };
112
+
113
+ const commits = planCommitMessages(plan);
114
+ expect(commits.length).toBeGreaterThan(0);
115
+ expect(commits.every(c => typeof c === 'string')).toBe(true);
116
+ });
117
+
118
+ it('returns empty for clean project', () => {
119
+ const plan = {
120
+ filesToMove: [],
121
+ hardcodedUrls: [],
122
+ interfacesToExtract: [],
123
+ functionsNeedingJsDoc: [],
124
+ };
125
+
126
+ const commits = planCommitMessages(plan);
127
+ expect(commits).toHaveLength(0);
128
+ });
129
+ });
130
+
131
+ describe('planCleanup', () => {
132
+ it('returns structured report with no side effects', async () => {
133
+ const mockGlob = vi.fn().mockResolvedValue([]);
134
+ const mockReadFile = vi.fn().mockResolvedValue('');
135
+
136
+ const result = await planCleanup('/project', {
137
+ glob: mockGlob,
138
+ readFile: mockReadFile,
139
+ });
140
+
141
+ expect(result).toMatchObject({
142
+ filesToMove: expect.any(Array),
143
+ hardcodedUrls: expect.any(Array),
144
+ interfacesToExtract: expect.any(Array),
145
+ functionsNeedingJsDoc: expect.any(Array),
146
+ plannedCommits: expect.any(Array),
147
+ });
148
+ });
149
+
150
+ it('handles multiple issue types', async () => {
151
+ const mockGlob = vi.fn()
152
+ .mockResolvedValueOnce(['src/services/userService.js']) // flat: services
153
+ .mockResolvedValueOnce([]) // flat: interfaces
154
+ .mockResolvedValueOnce([]) // flat: controllers
155
+ .mockResolvedValueOnce(['src/api.js']) // hardcoded urls
156
+ .mockResolvedValueOnce([]) // interfaces
157
+ .mockResolvedValueOnce([]); // jsdoc
158
+ const mockReadFile = vi.fn().mockResolvedValue(
159
+ 'const url = "http://localhost:3000";'
160
+ );
161
+
162
+ const result = await planCleanup('/project', {
163
+ glob: mockGlob,
164
+ readFile: mockReadFile,
165
+ });
166
+
167
+ expect(result.filesToMove.length + result.hardcodedUrls.length).toBeGreaterThan(0);
168
+ });
169
+
170
+ it('includes before/after preview for transforms', async () => {
171
+ const mockGlob = vi.fn()
172
+ .mockResolvedValueOnce([]) // flat: services
173
+ .mockResolvedValueOnce([]) // flat: interfaces
174
+ .mockResolvedValueOnce([]) // flat: controllers
175
+ .mockResolvedValueOnce(['src/api.js']) // hardcoded urls
176
+ .mockResolvedValueOnce([]) // interfaces
177
+ .mockResolvedValueOnce([]); // jsdoc
178
+ const mockReadFile = vi.fn().mockResolvedValue(
179
+ 'const api = "http://localhost:3000/api";'
180
+ );
181
+
182
+ const result = await planCleanup('/project', {
183
+ glob: mockGlob,
184
+ readFile: mockReadFile,
185
+ });
186
+
187
+ if (result.hardcodedUrls.length > 0) {
188
+ expect(result.hardcodedUrls[0].suggestedEnvVar).toBeDefined();
189
+ }
190
+ });
191
+
192
+ it('makes no fs.writeFileSync calls in dry-run', async () => {
193
+ const writeFile = vi.fn();
194
+ const mockGlob = vi.fn().mockResolvedValue([]);
195
+ const mockReadFile = vi.fn().mockResolvedValue('');
196
+
197
+ await planCleanup('/project', {
198
+ glob: mockGlob,
199
+ readFile: mockReadFile,
200
+ writeFile,
201
+ });
202
+
203
+ expect(writeFile).not.toHaveBeenCalled();
204
+ });
205
+
206
+ it('makes no git operations in dry-run', async () => {
207
+ const exec = vi.fn();
208
+ const mockGlob = vi.fn().mockResolvedValue([]);
209
+ const mockReadFile = vi.fn().mockResolvedValue('');
210
+
211
+ await planCleanup('/project', {
212
+ glob: mockGlob,
213
+ readFile: mockReadFile,
214
+ exec,
215
+ });
216
+
217
+ expect(exec).not.toHaveBeenCalled();
218
+ });
219
+ });
220
+ });