@eldrforge/ai-service 0.1.1 → 0.1.3

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 (46) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js.map +1 -0
  3. package/dist/src/ai.d.ts +55 -0
  4. package/{src/index.ts → dist/src/index.d.ts} +1 -2
  5. package/dist/src/interactive.d.ts +122 -0
  6. package/dist/src/logger.d.ts +19 -0
  7. package/dist/src/prompts/commit.d.ts +29 -0
  8. package/dist/src/prompts/index.d.ts +10 -0
  9. package/dist/src/prompts/release.d.ts +25 -0
  10. package/dist/src/prompts/review.d.ts +21 -0
  11. package/dist/src/types.d.ts +99 -0
  12. package/package.json +11 -8
  13. package/.github/dependabot.yml +0 -12
  14. package/.github/workflows/npm-publish.yml +0 -48
  15. package/.github/workflows/test.yml +0 -33
  16. package/eslint.config.mjs +0 -84
  17. package/src/ai.ts +0 -421
  18. package/src/interactive.ts +0 -562
  19. package/src/logger.ts +0 -69
  20. package/src/prompts/commit.ts +0 -85
  21. package/src/prompts/index.ts +0 -28
  22. package/src/prompts/instructions/commit.md +0 -133
  23. package/src/prompts/instructions/release.md +0 -188
  24. package/src/prompts/instructions/review.md +0 -169
  25. package/src/prompts/personas/releaser.md +0 -24
  26. package/src/prompts/personas/you.md +0 -55
  27. package/src/prompts/release.ts +0 -118
  28. package/src/prompts/review.ts +0 -72
  29. package/src/types.ts +0 -112
  30. package/tests/ai-complete-coverage.test.ts +0 -241
  31. package/tests/ai-create-completion.test.ts +0 -288
  32. package/tests/ai-edge-cases.test.ts +0 -221
  33. package/tests/ai-openai-error.test.ts +0 -35
  34. package/tests/ai-transcribe.test.ts +0 -169
  35. package/tests/ai.test.ts +0 -139
  36. package/tests/interactive-editor.test.ts +0 -253
  37. package/tests/interactive-secure-temp.test.ts +0 -264
  38. package/tests/interactive-user-choice.test.ts +0 -173
  39. package/tests/interactive-user-text.test.ts +0 -174
  40. package/tests/interactive.test.ts +0 -94
  41. package/tests/logger-noop.test.ts +0 -40
  42. package/tests/logger.test.ts +0 -122
  43. package/tests/prompts.test.ts +0 -179
  44. package/tsconfig.json +0 -35
  45. package/vite.config.ts +0 -69
  46. package/vitest.config.ts +0 -25
@@ -1,264 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { SecureTempFile, createSecureTempFile, cleanupTempFile } from '../src/interactive';
3
- import fs from 'fs/promises';
4
- import os from 'os';
5
-
6
- // Mock fs/promises
7
- vi.mock('fs/promises');
8
- vi.mock('os');
9
- vi.mock('../src/logger', () => ({
10
- getLogger: vi.fn(() => ({
11
- info: vi.fn(),
12
- error: vi.fn(),
13
- warn: vi.fn(),
14
- debug: vi.fn()
15
- }))
16
- }));
17
-
18
- describe('SecureTempFile', () => {
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
-
22
- // Mock os.tmpdir
23
- vi.mocked(os.tmpdir).mockReturnValue('/tmp');
24
-
25
- // Mock fs methods
26
- vi.mocked(fs.access).mockResolvedValue(undefined);
27
- vi.mocked(fs.mkdir).mockResolvedValue(undefined);
28
- vi.mocked(fs.unlink).mockResolvedValue(undefined);
29
- });
30
-
31
- afterEach(() => {
32
- vi.clearAllMocks();
33
- });
34
-
35
- describe('create', () => {
36
- it('should create a secure temp file', async () => {
37
- const mockFd = {
38
- writeFile: vi.fn().mockResolvedValue(undefined),
39
- readFile: vi.fn().mockResolvedValue('content'),
40
- close: vi.fn().mockResolvedValue(undefined),
41
- } as any;
42
-
43
- vi.mocked(fs.open).mockResolvedValue(mockFd);
44
-
45
- const tempFile = await SecureTempFile.create('test', '.txt');
46
-
47
- expect(tempFile).toBeDefined();
48
- expect(tempFile.path).toContain('/tmp/test_');
49
- expect(tempFile.path).toContain('.txt');
50
- expect(fs.open).toHaveBeenCalledWith(
51
- expect.stringContaining('/tmp/test_'),
52
- 'wx',
53
- 0o600
54
- );
55
- });
56
-
57
- it('should use custom prefix and extension', async () => {
58
- const mockFd = {
59
- writeFile: vi.fn(),
60
- readFile: vi.fn(),
61
- close: vi.fn(),
62
- } as any;
63
-
64
- vi.mocked(fs.open).mockResolvedValue(mockFd);
65
-
66
- const tempFile = await SecureTempFile.create('custom-prefix', '.md');
67
-
68
- expect(tempFile.path).toContain('custom-prefix_');
69
- expect(tempFile.path).toContain('.md');
70
- });
71
-
72
- it('should handle file creation errors', async () => {
73
- const error: any = new Error('Generic error');
74
- vi.mocked(fs.open).mockRejectedValue(error);
75
-
76
- await expect(SecureTempFile.create('test', '.txt'))
77
- .rejects.toThrow('Failed to create temporary file');
78
- });
79
-
80
- it('should throw error if file exists', async () => {
81
- const error: any = new Error('File exists');
82
- error.code = 'EEXIST';
83
- vi.mocked(fs.open).mockRejectedValue(error);
84
-
85
- await expect(SecureTempFile.create('test', '.txt'))
86
- .rejects.toThrow('Temporary file already exists');
87
- });
88
- });
89
-
90
- describe('writeContent', () => {
91
- it('should write content to temp file', async () => {
92
- const mockFd = {
93
- writeFile: vi.fn().mockResolvedValue(undefined),
94
- readFile: vi.fn(),
95
- close: vi.fn(),
96
- } as any;
97
-
98
- vi.mocked(fs.open).mockResolvedValue(mockFd);
99
-
100
- const tempFile = await SecureTempFile.create('test', '.txt');
101
- await tempFile.writeContent('test content');
102
-
103
- expect(mockFd.writeFile).toHaveBeenCalledWith('test content', 'utf8');
104
- });
105
-
106
- it('should throw if file is closed', async () => {
107
- const mockFd = {
108
- writeFile: vi.fn(),
109
- readFile: vi.fn(),
110
- close: vi.fn().mockResolvedValue(undefined),
111
- } as any;
112
-
113
- vi.mocked(fs.open).mockResolvedValue(mockFd);
114
-
115
- const tempFile = await SecureTempFile.create('test', '.txt');
116
- await tempFile.close();
117
-
118
- await expect(tempFile.writeContent('content'))
119
- .rejects.toThrow('Temp file is not available for writing');
120
- });
121
- });
122
-
123
- describe('readContent', () => {
124
- it('should read content from temp file', async () => {
125
- const mockFd = {
126
- writeFile: vi.fn(),
127
- readFile: vi.fn().mockResolvedValue('test content'),
128
- close: vi.fn(),
129
- } as any;
130
-
131
- vi.mocked(fs.open).mockResolvedValue(mockFd);
132
-
133
- const tempFile = await SecureTempFile.create('test', '.txt');
134
- const content = await tempFile.readContent();
135
-
136
- expect(content).toBe('test content');
137
- expect(mockFd.readFile).toHaveBeenCalledWith('utf8');
138
- });
139
-
140
- it('should throw if file is closed', async () => {
141
- const mockFd = {
142
- writeFile: vi.fn(),
143
- readFile: vi.fn(),
144
- close: vi.fn().mockResolvedValue(undefined),
145
- } as any;
146
-
147
- vi.mocked(fs.open).mockResolvedValue(mockFd);
148
-
149
- const tempFile = await SecureTempFile.create('test', '.txt');
150
- await tempFile.close();
151
-
152
- await expect(tempFile.readContent())
153
- .rejects.toThrow('Temp file is not available for reading');
154
- });
155
- });
156
-
157
- describe('cleanup', () => {
158
- it('should close and unlink temp file', async () => {
159
- const mockFd = {
160
- writeFile: vi.fn(),
161
- readFile: vi.fn(),
162
- close: vi.fn().mockResolvedValue(undefined),
163
- } as any;
164
-
165
- vi.mocked(fs.open).mockResolvedValue(mockFd);
166
-
167
- const tempFile = await SecureTempFile.create('test', '.txt');
168
- const filePath = tempFile.path;
169
-
170
- await tempFile.cleanup();
171
-
172
- expect(mockFd.close).toHaveBeenCalled();
173
- expect(fs.unlink).toHaveBeenCalledWith(filePath);
174
- });
175
-
176
- it('should handle cleanup errors gracefully', async () => {
177
- const mockFd = {
178
- writeFile: vi.fn(),
179
- readFile: vi.fn(),
180
- close: vi.fn().mockResolvedValue(undefined),
181
- } as any;
182
-
183
- vi.mocked(fs.open).mockResolvedValue(mockFd);
184
- vi.mocked(fs.unlink).mockRejectedValue(new Error('Unlink failed'));
185
-
186
- const tempFile = await SecureTempFile.create('test', '.txt');
187
-
188
- // Should not throw
189
- await expect(tempFile.cleanup()).resolves.not.toThrow();
190
- });
191
-
192
- it('should skip cleanup if already cleaned', async () => {
193
- const mockFd = {
194
- writeFile: vi.fn(),
195
- readFile: vi.fn(),
196
- close: vi.fn().mockResolvedValue(undefined),
197
- } as any;
198
-
199
- vi.mocked(fs.open).mockResolvedValue(mockFd);
200
-
201
- const tempFile = await SecureTempFile.create('test', '.txt');
202
-
203
- await tempFile.cleanup();
204
- vi.mocked(fs.unlink).mockClear();
205
-
206
- await tempFile.cleanup();
207
-
208
- expect(fs.unlink).not.toHaveBeenCalled();
209
- });
210
-
211
- it('should throw error when accessing path after cleanup', async () => {
212
- const mockFd = {
213
- writeFile: vi.fn(),
214
- readFile: vi.fn(),
215
- close: vi.fn().mockResolvedValue(undefined),
216
- } as any;
217
-
218
- vi.mocked(fs.open).mockResolvedValue(mockFd);
219
-
220
- const tempFile = await SecureTempFile.create('test', '.txt');
221
- await tempFile.cleanup();
222
-
223
- expect(() => tempFile.path).toThrow('Temp file has been cleaned up');
224
- });
225
- });
226
- });
227
-
228
- describe('createSecureTempFile', () => {
229
- it('should create and close temp file', async () => {
230
- const mockFd = {
231
- writeFile: vi.fn(),
232
- readFile: vi.fn(),
233
- close: vi.fn().mockResolvedValue(undefined),
234
- } as any;
235
-
236
- vi.mocked(os.tmpdir).mockReturnValue('/tmp');
237
- vi.mocked(fs.access).mockResolvedValue(undefined);
238
- vi.mocked(fs.open).mockResolvedValue(mockFd);
239
-
240
- const path = await createSecureTempFile('test', '.txt');
241
-
242
- expect(path).toContain('/tmp/test_');
243
- expect(mockFd.close).toHaveBeenCalled();
244
- });
245
- });
246
-
247
- describe('cleanupTempFile', () => {
248
- it('should unlink the file', async () => {
249
- vi.mocked(fs.unlink).mockResolvedValue(undefined);
250
-
251
- await cleanupTempFile('/tmp/test.txt');
252
-
253
- expect(fs.unlink).toHaveBeenCalledWith('/tmp/test.txt');
254
- });
255
-
256
- it('should handle ENOENT errors gracefully', async () => {
257
- const error: any = new Error('ENOENT');
258
- error.code = 'ENOENT';
259
- vi.mocked(fs.unlink).mockRejectedValue(error);
260
-
261
- await expect(cleanupTempFile('/tmp/test.txt')).resolves.not.toThrow();
262
- });
263
- });
264
-
@@ -1,173 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { getUserChoice, STANDARD_CHOICES } from '../src/interactive';
3
-
4
- // Mock logger
5
- const mockLoggerInstance = {
6
- info: vi.fn(),
7
- error: vi.fn(),
8
- warn: vi.fn(),
9
- debug: vi.fn(),
10
- };
11
-
12
- vi.mock('../src/logger', () => ({
13
- getLogger: vi.fn(() => mockLoggerInstance),
14
- }));
15
-
16
- describe('getUserChoice', () => {
17
- let originalIsTTY: boolean | undefined;
18
- let originalSetRawMode: any;
19
- let originalResume: any;
20
- let originalPause: any;
21
- let originalRef: any;
22
- let originalUnref: any;
23
- let originalOn: any;
24
- let originalRemoveListener: any;
25
-
26
- beforeEach(() => {
27
- vi.clearAllMocks();
28
-
29
- // Save original stdin methods
30
- originalIsTTY = process.stdin.isTTY;
31
- originalSetRawMode = process.stdin.setRawMode;
32
- originalResume = process.stdin.resume;
33
- originalPause = process.stdin.pause;
34
- originalRef = process.stdin.ref;
35
- originalUnref = process.stdin.unref;
36
- originalOn = process.stdin.on;
37
- originalRemoveListener = process.stdin.removeListener;
38
- });
39
-
40
- afterEach(() => {
41
- // Restore stdin
42
- Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true, writable: true });
43
- process.stdin.setRawMode = originalSetRawMode;
44
- process.stdin.resume = originalResume;
45
- process.stdin.pause = originalPause;
46
- process.stdin.ref = originalRef;
47
- process.stdin.unref = originalUnref;
48
- process.stdin.on = originalOn;
49
- process.stdin.removeListener = originalRemoveListener;
50
- });
51
-
52
- it('should return default when stdin is not TTY', async () => {
53
- Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true, writable: true });
54
-
55
- const result = await getUserChoice('Test prompt', [
56
- { key: 'a', label: 'Option A' },
57
- { key: 'b', label: 'Option B' },
58
- ]);
59
-
60
- expect(result).toBe('s');
61
- expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining('STDIN is piped'));
62
- });
63
-
64
- it('should show error suggestions when not TTY', async () => {
65
- Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true, writable: true });
66
-
67
- await getUserChoice(
68
- 'Test prompt',
69
- [{ key: 'a', label: 'Option A' }],
70
- { nonTtyErrorSuggestions: ['Use --dry-run', 'Run in terminal'] }
71
- );
72
-
73
- expect(mockLoggerInstance.error).toHaveBeenCalledWith(' • Use --dry-run');
74
- expect(mockLoggerInstance.error).toHaveBeenCalledWith(' • Run in terminal');
75
- });
76
-
77
- it('should display prompt and choices', async () => {
78
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
79
-
80
- // Mock stdin methods
81
- process.stdin.setRawMode = vi.fn();
82
- process.stdin.resume = vi.fn();
83
- process.stdin.pause = vi.fn();
84
- process.stdin.ref = vi.fn();
85
- process.stdin.unref = vi.fn();
86
- process.stdin.removeListener = vi.fn();
87
-
88
- let dataCallback: any;
89
- process.stdin.on = vi.fn((event, callback) => {
90
- if (event === 'data') {
91
- dataCallback = callback;
92
- // Simulate user pressing 'a'
93
- setTimeout(() => dataCallback(Buffer.from('a')), 10);
94
- }
95
- return process.stdin;
96
- });
97
-
98
- const result = await getUserChoice('What to do?', [
99
- { key: 'a', label: 'Action A' },
100
- { key: 'b', label: 'Action B' },
101
- ]);
102
-
103
- expect(result).toBe('a');
104
- expect(mockLoggerInstance.info).toHaveBeenCalledWith('What to do?');
105
- expect(mockLoggerInstance.info).toHaveBeenCalledWith(' [a] Action A');
106
- expect(mockLoggerInstance.info).toHaveBeenCalledWith(' [b] Action B');
107
- });
108
-
109
- it('should handle user selecting first choice', async () => {
110
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
111
-
112
- process.stdin.setRawMode = vi.fn();
113
- process.stdin.resume = vi.fn();
114
- process.stdin.pause = vi.fn();
115
- process.stdin.ref = vi.fn();
116
- process.stdin.unref = vi.fn();
117
- process.stdin.removeListener = vi.fn();
118
-
119
- process.stdin.on = vi.fn((event, callback) => {
120
- if (event === 'data') {
121
- setTimeout(() => callback(Buffer.from('c')), 10);
122
- }
123
- return process.stdin;
124
- });
125
-
126
- const result = await getUserChoice('Confirm?', [STANDARD_CHOICES.CONFIRM, STANDARD_CHOICES.SKIP]);
127
-
128
- expect(result).toBe('c');
129
- });
130
-
131
- it('should cleanup stdin on completion', async () => {
132
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
133
-
134
- process.stdin.setRawMode = vi.fn();
135
- process.stdin.resume = vi.fn();
136
- process.stdin.pause = vi.fn();
137
- process.stdin.ref = vi.fn();
138
- process.stdin.unref = vi.fn();
139
- const mockRemoveListener = vi.fn();
140
- process.stdin.removeListener = mockRemoveListener;
141
-
142
- process.stdin.on = vi.fn((event, callback) => {
143
- if (event === 'data') {
144
- setTimeout(() => callback(Buffer.from('s')), 10);
145
- }
146
- return process.stdin;
147
- });
148
-
149
- await getUserChoice('Test?', [STANDARD_CHOICES.SKIP]);
150
-
151
- expect(mockRemoveListener).toHaveBeenCalled();
152
- expect(process.stdin.pause).toHaveBeenCalled();
153
- });
154
-
155
- it('should handle errors during input setup', async () => {
156
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
157
-
158
- process.stdin.setRawMode = vi.fn(() => {
159
- throw new Error('Setup failed');
160
- });
161
- process.stdin.resume = vi.fn();
162
- process.stdin.pause = vi.fn();
163
- process.stdin.ref = vi.fn();
164
- process.stdin.unref = vi.fn();
165
- process.stdin.removeListener = vi.fn();
166
- process.stdin.on = vi.fn();
167
-
168
- await expect(
169
- getUserChoice('Test?', [{ key: 'a', label: 'Option A' }])
170
- ).rejects.toThrow('Setup failed');
171
- });
172
- });
173
-
@@ -1,174 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { getUserTextInput } from '../src/interactive';
3
-
4
- // Mock logger
5
- const mockLoggerInstance = {
6
- info: vi.fn(),
7
- error: vi.fn(),
8
- warn: vi.fn(),
9
- debug: vi.fn(),
10
- };
11
-
12
- vi.mock('../src/logger', () => ({
13
- getLogger: vi.fn(() => mockLoggerInstance),
14
- }));
15
-
16
- describe('getUserTextInput', () => {
17
- let originalIsTTY: boolean | undefined;
18
- let originalSetEncoding: any;
19
- let originalResume: any;
20
- let originalPause: any;
21
- let originalRef: any;
22
- let originalUnref: any;
23
- let originalOn: any;
24
- let originalRemoveListener: any;
25
-
26
- beforeEach(() => {
27
- vi.clearAllMocks();
28
-
29
- // Save original stdin methods
30
- originalIsTTY = process.stdin.isTTY;
31
- originalSetEncoding = process.stdin.setEncoding;
32
- originalResume = process.stdin.resume;
33
- originalPause = process.stdin.pause;
34
- originalRef = process.stdin.ref;
35
- originalUnref = process.stdin.unref;
36
- originalOn = process.stdin.on;
37
- originalRemoveListener = process.stdin.removeListener;
38
- });
39
-
40
- afterEach(() => {
41
- // Restore stdin
42
- Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true, writable: true });
43
- process.stdin.setEncoding = originalSetEncoding;
44
- process.stdin.resume = originalResume;
45
- process.stdin.pause = originalPause;
46
- process.stdin.ref = originalRef;
47
- process.stdin.unref = originalUnref;
48
- process.stdin.on = originalOn;
49
- process.stdin.removeListener = originalRemoveListener;
50
- });
51
-
52
- it('should throw error when stdin is not TTY', async () => {
53
- Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true, writable: true });
54
-
55
- await expect(
56
- getUserTextInput('Enter text:')
57
- ).rejects.toThrow('Interactive text input requires a terminal');
58
-
59
- expect(mockLoggerInstance.error).toHaveBeenCalledWith(expect.stringContaining('STDIN is piped'));
60
- });
61
-
62
- it('should show error suggestions when not TTY', async () => {
63
- Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true, writable: true });
64
-
65
- await expect(
66
- getUserTextInput('Enter text:', {
67
- nonTtyErrorSuggestions: ['Use file input', 'Run interactively'],
68
- })
69
- ).rejects.toThrow();
70
-
71
- expect(mockLoggerInstance.error).toHaveBeenCalledWith(' • Use file input');
72
- expect(mockLoggerInstance.error).toHaveBeenCalledWith(' • Run interactively');
73
- });
74
-
75
- it('should accept text input from user', async () => {
76
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
77
-
78
- process.stdin.setEncoding = vi.fn();
79
- process.stdin.resume = vi.fn();
80
- process.stdin.pause = vi.fn();
81
- process.stdin.ref = vi.fn();
82
- process.stdin.unref = vi.fn();
83
- process.stdin.removeListener = vi.fn();
84
-
85
- process.stdin.on = vi.fn((event, callback) => {
86
- if (event === 'data') {
87
- setTimeout(() => callback('user input text\n'), 10);
88
- }
89
- return process.stdin;
90
- });
91
-
92
- const result = await getUserTextInput('Enter feedback:');
93
-
94
- expect(result).toBe('user input text');
95
- expect(mockLoggerInstance.info).toHaveBeenCalledWith('Enter feedback:');
96
- });
97
-
98
- it('should reject empty input', async () => {
99
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
100
-
101
- process.stdin.setEncoding = vi.fn();
102
- process.stdin.resume = vi.fn();
103
- process.stdin.pause = vi.fn();
104
- process.stdin.ref = vi.fn();
105
- process.stdin.unref = vi.fn();
106
- process.stdin.removeListener = vi.fn();
107
-
108
- process.stdin.on = vi.fn((event, callback) => {
109
- if (event === 'data') {
110
- setTimeout(() => callback('\n'), 10);
111
- }
112
- return process.stdin;
113
- });
114
-
115
- await expect(
116
- getUserTextInput('Enter text:')
117
- ).rejects.toThrow('Empty input received');
118
-
119
- expect(mockLoggerInstance.warn).toHaveBeenCalledWith(expect.stringContaining('Empty input'));
120
- });
121
-
122
- it('should cleanup stdin on completion', async () => {
123
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
124
-
125
- process.stdin.setEncoding = vi.fn();
126
- process.stdin.resume = vi.fn();
127
- const mockPause = vi.fn();
128
- process.stdin.pause = mockPause;
129
- process.stdin.ref = vi.fn();
130
- process.stdin.unref = vi.fn();
131
- const mockRemoveListener = vi.fn();
132
- process.stdin.removeListener = mockRemoveListener;
133
-
134
- process.stdin.on = vi.fn((event, callback) => {
135
- if (event === 'data') {
136
- setTimeout(() => callback('text\n'), 10);
137
- }
138
- return process.stdin;
139
- });
140
-
141
- await getUserTextInput('Enter text:');
142
-
143
- expect(mockRemoveListener).toHaveBeenCalled();
144
- expect(mockPause).toHaveBeenCalled();
145
- });
146
-
147
- it('should handle input processing errors', async () => {
148
- Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true, writable: true });
149
-
150
- process.stdin.setEncoding = vi.fn();
151
- process.stdin.resume = vi.fn();
152
- process.stdin.pause = vi.fn();
153
- process.stdin.ref = vi.fn();
154
- process.stdin.unref = vi.fn();
155
- process.stdin.removeListener = vi.fn();
156
-
157
- process.stdin.on = vi.fn((event, callback) => {
158
- if (event === 'data') {
159
- setTimeout(() => {
160
- try {
161
- callback(null); // Invalid input
162
- } catch (e) {
163
- // Expected
164
- }
165
- }, 10);
166
- }
167
- return process.stdin;
168
- });
169
-
170
- // This test verifies error handling exists
171
- expect(process.stdin.on).toBeDefined();
172
- });
173
- });
174
-
@@ -1,94 +0,0 @@
1
- import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
2
- import { requireTTY } from '../src/interactive';
3
- import type { Logger } from '../src/types';
4
-
5
- // Mock logger for tests
6
- vi.mock('../src/logger', () => ({
7
- getLogger: vi.fn(() => ({
8
- info: vi.fn(),
9
- error: vi.fn(),
10
- warn: vi.fn(),
11
- debug: vi.fn()
12
- }))
13
- }));
14
-
15
- describe('Interactive Utility Module', () => {
16
- let originalIsTTY: boolean | undefined;
17
-
18
- beforeEach(() => {
19
- vi.clearAllMocks();
20
- originalIsTTY = process.stdin.isTTY;
21
- });
22
-
23
- afterEach(() => {
24
- if (originalIsTTY !== undefined) {
25
- Object.defineProperty(process.stdin, 'isTTY', {
26
- value: originalIsTTY,
27
- configurable: true,
28
- writable: true
29
- });
30
- }
31
- });
32
-
33
- describe('requireTTY', () => {
34
- it('should not throw when stdin is a TTY', () => {
35
- Object.defineProperty(process.stdin, 'isTTY', {
36
- value: true,
37
- configurable: true,
38
- writable: true
39
- });
40
-
41
- expect(() => requireTTY()).not.toThrow();
42
- });
43
-
44
- it('should throw when stdin is not a TTY', () => {
45
- Object.defineProperty(process.stdin, 'isTTY', {
46
- value: false,
47
- configurable: true,
48
- writable: true
49
- });
50
-
51
- expect(() => requireTTY()).toThrow('Interactive mode requires a terminal');
52
- });
53
-
54
- it('should throw custom error message', () => {
55
- Object.defineProperty(process.stdin, 'isTTY', {
56
- value: false,
57
- configurable: true,
58
- writable: true
59
- });
60
-
61
- expect(() => requireTTY('Custom error message')).toThrow('Custom error message');
62
- });
63
-
64
- it('should accept optional logger', () => {
65
- const mockLogger: Logger = {
66
- info: vi.fn(),
67
- error: vi.fn(),
68
- warn: vi.fn(),
69
- debug: vi.fn()
70
- };
71
-
72
- Object.defineProperty(process.stdin, 'isTTY', {
73
- value: false,
74
- configurable: true,
75
- writable: true
76
- });
77
-
78
- expect(() => requireTTY('Test error', mockLogger)).toThrow();
79
- expect(mockLogger.error).toHaveBeenCalled();
80
- });
81
- });
82
-
83
- describe('STANDARD_CHOICES', () => {
84
- it('should export standard choice constants', async () => {
85
- const { STANDARD_CHOICES } = await import('../src/interactive');
86
-
87
- expect(STANDARD_CHOICES.CONFIRM).toEqual({ key: 'c', label: 'Confirm and proceed' });
88
- expect(STANDARD_CHOICES.EDIT).toEqual({ key: 'e', label: 'Edit in editor' });
89
- expect(STANDARD_CHOICES.SKIP).toEqual({ key: 's', label: 'Skip and abort' });
90
- expect(STANDARD_CHOICES.IMPROVE).toEqual({ key: 'i', label: 'Improve with LLM feedback' });
91
- });
92
- });
93
- });
94
-