prjct-cli 0.10.5 → 0.10.8
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.
- package/CHANGELOG.md +83 -0
- package/core/__tests__/agentic/prompt-builder.test.js +244 -0
- package/core/__tests__/utils/output.test.js +163 -0
- package/core/agentic/agent-router.js +45 -173
- package/core/agentic/context-builder.js +20 -11
- package/core/agentic/context-filter.js +118 -313
- package/core/agentic/prompt-builder.js +79 -46
- package/core/commands.js +121 -637
- package/core/domain/agent-generator.js +55 -4
- package/core/domain/analyzer.js +122 -0
- package/core/domain/context-estimator.js +32 -53
- package/core/domain/smart-cache.js +2 -1
- package/core/domain/task-analyzer.js +75 -146
- package/core/domain/task-stack.js +2 -1
- package/core/utils/logger.js +64 -0
- package/core/utils/output.js +54 -0
- package/package.json +1 -1
- package/templates/agentic/agent-routing.md +78 -0
- package/templates/agentic/context-filtering.md +77 -0
- package/templates/analysis/project-analysis.md +78 -0
- package/templates/global/CLAUDE.md +137 -135
- package/core/domain/tech-detector.js +0 -365
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.8] - 2025-11-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Minimal Output System** - New `core/utils/output.js` module for clean, minimal CLI output
|
|
8
|
+
- `spin(msg)` - Spinner animation while working
|
|
9
|
+
- `done(msg)` - Single-line success output (✓)
|
|
10
|
+
- `fail(msg)` - Single-line error output (✗)
|
|
11
|
+
- `warn(msg)` - Single-line warning output (⚠)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Reduced CLI Verbosity** - All major commands now show spinner + 1 line result instead of verbose multi-line output
|
|
16
|
+
- `/p:now` - Shows only task name and status
|
|
17
|
+
- `/p:done` - Shows task and duration in one line
|
|
18
|
+
- `/p:next` - Shows queue count only
|
|
19
|
+
- `/p:ship` - Spinner during steps, single line result with version
|
|
20
|
+
- `/p:feature` - Shows task count created
|
|
21
|
+
- `/p:init` - Spinner during initialization, minimal completion message
|
|
22
|
+
- `/p:bug` - Shows severity in one line
|
|
23
|
+
- `/p:context` - Shows task and queue summary
|
|
24
|
+
- `/p:recap` - Shows shipped/queue/ideas counts
|
|
25
|
+
- `/p:stuck` - Logs issue with minimal output
|
|
26
|
+
- `/p:design` - Shows design type created
|
|
27
|
+
- `/p:cleanup` - Shows items cleaned count
|
|
28
|
+
- `/p:progress` - Shows period metrics summary
|
|
29
|
+
- `/p:roadmap` - Shows features count
|
|
30
|
+
|
|
31
|
+
### Improved
|
|
32
|
+
|
|
33
|
+
- **Resource Efficiency** - Reduced terminal output significantly, consuming less resources
|
|
34
|
+
- **Cleaner UX** - Focus on essential information, no verbose step-by-step logs
|
|
35
|
+
|
|
36
|
+
## [0.10.7] - 2025-11-27
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **Critical Git Safety** - Added explicit protections against destructive git operations
|
|
41
|
+
- Prohibits `git checkout` without checking for uncommitted changes first
|
|
42
|
+
- Prohibits `git reset --hard` and `git clean -fd` operations
|
|
43
|
+
- Requires `git status` check before any git operation
|
|
44
|
+
- Prevents loss of user's work through accidental git operations
|
|
45
|
+
|
|
46
|
+
- **Anti-Hallucination Improvements** - Enhanced instructions to prevent code hallucinations
|
|
47
|
+
- Explicit "READ CODE FIRST" requirement before modifying files
|
|
48
|
+
- Mandatory pattern matching with existing codebase
|
|
49
|
+
- Improved context awareness with file listings
|
|
50
|
+
- Better state filtering to preserve critical information (up to 2000 chars for critical files)
|
|
51
|
+
|
|
52
|
+
### Enhanced
|
|
53
|
+
|
|
54
|
+
- **Pattern Detection & Enforcement** - Automatic project pattern detection and enforcement
|
|
55
|
+
- Analysis file (`analysis/repo-summary.md`) automatically loaded for all code modification commands
|
|
56
|
+
- Project patterns extracted and displayed in prompts
|
|
57
|
+
- Explicit instructions to follow existing patterns exactly
|
|
58
|
+
- Step-by-step guide to match code style, structure, and conventions
|
|
59
|
+
|
|
60
|
+
- **Context Improvements** - Better context loading and presentation
|
|
61
|
+
- Analysis included automatically for: now, done, next, ship, work, build, design, cleanup, fix, test
|
|
62
|
+
- Project files listed with instructions to use Read tool
|
|
63
|
+
- State filtering improved to show full content for critical files (now, next, context, analysis)
|
|
64
|
+
- Better truncation strategy (full content < 1000 chars, 2000 chars for critical files)
|
|
65
|
+
|
|
66
|
+
- **Prompt Builder Enhancements** - More comprehensive instructions for Claude
|
|
67
|
+
- Anti-hallucination section with 7 critical rules
|
|
68
|
+
- Git safety rules with explicit prohibitions
|
|
69
|
+
- Pattern detection and enforcement section
|
|
70
|
+
- Available project files listing
|
|
71
|
+
- Improved enforcement section with pattern matching requirement
|
|
72
|
+
|
|
73
|
+
## [0.10.6] - 2025-11-27
|
|
74
|
+
|
|
75
|
+
### Enhanced
|
|
76
|
+
|
|
77
|
+
- **Global CLAUDE.md Template** - Improved to help Claude use prjct-cli better
|
|
78
|
+
- Quick command reference table with examples
|
|
79
|
+
- Recommended workflow (sync → feature → now → done → ship)
|
|
80
|
+
- Common usage patterns with examples
|
|
81
|
+
- Anti-patterns table (what NOT to do)
|
|
82
|
+
- Using agents effectively section
|
|
83
|
+
- Simplified file structure overview
|
|
84
|
+
- Error handling table
|
|
85
|
+
|
|
3
86
|
## [0.10.5] - 2025-11-27
|
|
4
87
|
|
|
5
88
|
### Enhanced
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Builder Tests
|
|
3
|
+
* Tests for optimized prompts with compressed rules
|
|
4
|
+
*
|
|
5
|
+
* OPTIMIZED: Tests updated to match compressed prompt structure
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
9
|
+
import promptBuilder from '../../agentic/prompt-builder.js'
|
|
10
|
+
|
|
11
|
+
describe('PromptBuilder', () => {
|
|
12
|
+
let builder
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
builder = promptBuilder
|
|
16
|
+
builder._currentContext = null
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('Critical Rules (Compressed)', () => {
|
|
20
|
+
it('should include git safety rules', () => {
|
|
21
|
+
builder._currentContext = { files: [], filteredSize: 0 }
|
|
22
|
+
const rules = builder.buildCriticalRules()
|
|
23
|
+
|
|
24
|
+
expect(rules).toContain('GIT SAFETY')
|
|
25
|
+
expect(rules).toContain('checkout')
|
|
26
|
+
expect(rules).toContain('reset')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should include read-first requirement', () => {
|
|
30
|
+
builder._currentContext = { files: [], filteredSize: 0 }
|
|
31
|
+
const rules = builder.buildCriticalRules()
|
|
32
|
+
|
|
33
|
+
expect(rules).toContain('READ FIRST')
|
|
34
|
+
expect(rules).toContain('Read tool')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should include pattern matching requirement', () => {
|
|
38
|
+
builder._currentContext = { files: [], filteredSize: 0 }
|
|
39
|
+
const rules = builder.buildCriticalRules()
|
|
40
|
+
|
|
41
|
+
expect(rules).toContain('MATCH PATTERNS')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should include no-hallucination rules', () => {
|
|
45
|
+
builder._currentContext = { files: [], filteredSize: 0 }
|
|
46
|
+
const rules = builder.buildCriticalRules()
|
|
47
|
+
|
|
48
|
+
expect(rules).toContain('NO HALLUCINATIONS')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should show file count in context', () => {
|
|
52
|
+
builder._currentContext = { files: ['a.js', 'b.js', 'c.js'] }
|
|
53
|
+
const rules = builder.buildCriticalRules()
|
|
54
|
+
|
|
55
|
+
expect(rules).toContain('3 files available')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('State Filtering', () => {
|
|
60
|
+
it('should preserve full content for critical files', () => {
|
|
61
|
+
const state = {
|
|
62
|
+
now: '# NOW\n\n**Test task**\n\nStarted: 2025-01-01',
|
|
63
|
+
next: '# NEXT\n\n## Priority Queue\n\n1. Task 1',
|
|
64
|
+
context: 'Project context information',
|
|
65
|
+
analysis: 'Stack: Node.js\nPatterns: ES6 modules',
|
|
66
|
+
metrics: 'Some metrics data'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const filtered = builder.filterRelevantState(state)
|
|
70
|
+
|
|
71
|
+
expect(filtered).toContain('### now')
|
|
72
|
+
expect(filtered).toContain('Test task')
|
|
73
|
+
expect(filtered).toContain('### next')
|
|
74
|
+
expect(filtered).toContain('### context')
|
|
75
|
+
expect(filtered).toContain('### analysis')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should truncate large non-critical files', () => {
|
|
79
|
+
const largeContent = 'x'.repeat(2000)
|
|
80
|
+
const state = {
|
|
81
|
+
now: '# NOW\n\n**Task**',
|
|
82
|
+
largeFile: largeContent
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const filtered = builder.filterRelevantState(state)
|
|
86
|
+
|
|
87
|
+
expect(filtered).toContain('### now')
|
|
88
|
+
expect(filtered).toContain('### largeFile')
|
|
89
|
+
expect(filtered).toContain('truncated')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should return null for empty state', () => {
|
|
93
|
+
const filtered = builder.filterRelevantState({})
|
|
94
|
+
expect(filtered).toBeNull()
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('Context Filtering by Command Type', () => {
|
|
99
|
+
it('should include patterns for code commands', () => {
|
|
100
|
+
const template = {
|
|
101
|
+
frontmatter: { description: 'Build feature', name: 'p:build' },
|
|
102
|
+
content: '## Flow\nBuild something'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const context = { projectPath: '/test', files: ['file1.js'] }
|
|
106
|
+
const state = { analysis: 'Stack: Node.js, React' }
|
|
107
|
+
|
|
108
|
+
const prompt = builder.build(template, context, state)
|
|
109
|
+
|
|
110
|
+
expect(prompt).toContain('PATTERNS')
|
|
111
|
+
expect(prompt).toContain('Node.js')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should NOT include patterns for non-code commands', () => {
|
|
115
|
+
const template = {
|
|
116
|
+
frontmatter: { description: 'Show current task', name: 'p:now' },
|
|
117
|
+
content: '## Flow\nShow task'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const context = { projectPath: '/test', files: ['file1.js'] }
|
|
121
|
+
const state = { analysis: 'Stack: Node.js, React' }
|
|
122
|
+
|
|
123
|
+
const prompt = builder.build(template, context, state)
|
|
124
|
+
|
|
125
|
+
// Non-code commands should NOT include patterns section
|
|
126
|
+
expect(prompt).not.toContain('## PATTERNS')
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('Project Files Listing', () => {
|
|
131
|
+
it('should list available files when context has files', () => {
|
|
132
|
+
const template = {
|
|
133
|
+
frontmatter: { description: 'Test command' },
|
|
134
|
+
content: '## Flow\nDo something'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const context = {
|
|
138
|
+
projectPath: '/test',
|
|
139
|
+
files: ['src/file1.js', 'src/file2.js', 'tests/test1.js']
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const state = {}
|
|
143
|
+
|
|
144
|
+
const prompt = builder.build(template, context, state)
|
|
145
|
+
|
|
146
|
+
expect(prompt).toContain('AVAILABLE PROJECT FILES')
|
|
147
|
+
expect(prompt).toContain('file1.js')
|
|
148
|
+
expect(prompt).toContain('Read')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should show project path when no files listed', () => {
|
|
152
|
+
const template = {
|
|
153
|
+
frontmatter: { description: 'Test command' },
|
|
154
|
+
content: '## Flow\nDo something'
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const context = { projectPath: '/test/project' }
|
|
158
|
+
const state = {}
|
|
159
|
+
|
|
160
|
+
const prompt = builder.build(template, context, state)
|
|
161
|
+
|
|
162
|
+
expect(prompt).toContain('PROJECT FILES')
|
|
163
|
+
expect(prompt).toContain('/test/project')
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('Build Complete Prompt', () => {
|
|
168
|
+
it('should include all critical sections', () => {
|
|
169
|
+
const template = {
|
|
170
|
+
frontmatter: {
|
|
171
|
+
description: 'Test command',
|
|
172
|
+
'allowed-tools': ['Read', 'Write']
|
|
173
|
+
},
|
|
174
|
+
content: '## Flow\n1. Do step 1\n2. Do step 2'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const context = {
|
|
178
|
+
projectPath: '/test',
|
|
179
|
+
params: { task: 'test task' },
|
|
180
|
+
files: ['file1.js']
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const state = { now: '# NOW\n\n**Current task**' }
|
|
184
|
+
|
|
185
|
+
const prompt = builder.build(template, context, state)
|
|
186
|
+
|
|
187
|
+
expect(prompt).toContain('TASK:')
|
|
188
|
+
expect(prompt).toContain('TOOLS:')
|
|
189
|
+
expect(prompt).toContain('Flow')
|
|
190
|
+
expect(prompt).toContain('RULES (CRITICAL)')
|
|
191
|
+
expect(prompt).toContain('AVAILABLE PROJECT FILES')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should be concise (under 2000 chars for simple prompt)', () => {
|
|
195
|
+
const template = {
|
|
196
|
+
frontmatter: { description: 'Test', 'allowed-tools': ['Read'] },
|
|
197
|
+
content: '## Flow\n1. Test'
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const context = { projectPath: '/test', files: ['a.js'] }
|
|
201
|
+
const state = {}
|
|
202
|
+
|
|
203
|
+
const prompt = builder.build(template, context, state)
|
|
204
|
+
|
|
205
|
+
// Optimized prompts should be under 2000 chars for simple cases
|
|
206
|
+
expect(prompt.length).toBeLessThan(2000)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('Plan Mode (Compressed)', () => {
|
|
211
|
+
it('should include compact plan mode instructions', () => {
|
|
212
|
+
const template = {
|
|
213
|
+
frontmatter: { description: 'Test' },
|
|
214
|
+
content: '## Flow\nTest'
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const context = { projectPath: '/test' }
|
|
218
|
+
const state = {}
|
|
219
|
+
const planInfo = { isPlanning: true, allowedTools: ['Read', 'Glob'] }
|
|
220
|
+
|
|
221
|
+
const prompt = builder.build(template, context, state, null, null, null, null, planInfo)
|
|
222
|
+
|
|
223
|
+
expect(prompt).toContain('PLAN MODE')
|
|
224
|
+
expect(prompt).toContain('Read-only')
|
|
225
|
+
expect(prompt).toContain('Tools: Read, Glob')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should include approval required section', () => {
|
|
229
|
+
const template = {
|
|
230
|
+
frontmatter: { description: 'Test' },
|
|
231
|
+
content: '## Flow\nTest'
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const context = { projectPath: '/test' }
|
|
235
|
+
const state = {}
|
|
236
|
+
const planInfo = { requiresApproval: true }
|
|
237
|
+
|
|
238
|
+
const prompt = builder.build(template, context, state, null, null, null, null, planInfo)
|
|
239
|
+
|
|
240
|
+
expect(prompt).toContain('APPROVAL REQUIRED')
|
|
241
|
+
expect(prompt).toContain('confirmation')
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Module Tests
|
|
3
|
+
* Minimal output system for prjct-cli
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const out = require('../../utils/output')
|
|
7
|
+
|
|
8
|
+
describe('Output Module', () => {
|
|
9
|
+
let consoleLogSpy
|
|
10
|
+
let stdoutWriteSpy
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Mock console.log
|
|
14
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
15
|
+
// Mock process.stdout.write
|
|
16
|
+
stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => {})
|
|
17
|
+
// Ensure spinner is stopped before each test
|
|
18
|
+
out.stop()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
consoleLogSpy.mockRestore()
|
|
23
|
+
stdoutWriteSpy.mockRestore()
|
|
24
|
+
out.stop()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('done()', () => {
|
|
28
|
+
it('should output success message with checkmark', () => {
|
|
29
|
+
out.done('task completed')
|
|
30
|
+
|
|
31
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
32
|
+
const output = consoleLogSpy.mock.calls[0][0]
|
|
33
|
+
expect(output).toContain('✓')
|
|
34
|
+
expect(output).toContain('task completed')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should truncate long messages', () => {
|
|
38
|
+
const longMessage = 'a'.repeat(100)
|
|
39
|
+
out.done(longMessage)
|
|
40
|
+
|
|
41
|
+
const output = consoleLogSpy.mock.calls[0][0]
|
|
42
|
+
// Should be truncated to ~65 chars + checkmark
|
|
43
|
+
expect(output.length).toBeLessThan(80)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should return self for chaining', () => {
|
|
47
|
+
const result = out.done('test')
|
|
48
|
+
expect(result).toBe(out)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('fail()', () => {
|
|
53
|
+
it('should output error message with X mark', () => {
|
|
54
|
+
out.fail('something failed')
|
|
55
|
+
|
|
56
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
57
|
+
const output = consoleLogSpy.mock.calls[0][0]
|
|
58
|
+
expect(output).toContain('✗')
|
|
59
|
+
expect(output).toContain('something failed')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should truncate long error messages', () => {
|
|
63
|
+
const longMessage = 'error '.repeat(50)
|
|
64
|
+
out.fail(longMessage)
|
|
65
|
+
|
|
66
|
+
const output = consoleLogSpy.mock.calls[0][0]
|
|
67
|
+
expect(output.length).toBeLessThan(80)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should return self for chaining', () => {
|
|
71
|
+
const result = out.fail('test')
|
|
72
|
+
expect(result).toBe(out)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('warn()', () => {
|
|
77
|
+
it('should output warning message with warning symbol', () => {
|
|
78
|
+
out.warn('be careful')
|
|
79
|
+
|
|
80
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1)
|
|
81
|
+
const output = consoleLogSpy.mock.calls[0][0]
|
|
82
|
+
expect(output).toContain('⚠')
|
|
83
|
+
expect(output).toContain('be careful')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should return self for chaining', () => {
|
|
87
|
+
const result = out.warn('test')
|
|
88
|
+
expect(result).toBe(out)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('spin()', () => {
|
|
93
|
+
it('should start spinner with message', () => {
|
|
94
|
+
vi.useFakeTimers()
|
|
95
|
+
|
|
96
|
+
out.spin('loading')
|
|
97
|
+
|
|
98
|
+
// Advance timer to trigger interval
|
|
99
|
+
vi.advanceTimersByTime(100)
|
|
100
|
+
|
|
101
|
+
expect(stdoutWriteSpy).toHaveBeenCalled()
|
|
102
|
+
const output = stdoutWriteSpy.mock.calls[0][0]
|
|
103
|
+
expect(output).toContain('loading')
|
|
104
|
+
|
|
105
|
+
out.stop()
|
|
106
|
+
vi.useRealTimers()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should return self for chaining', () => {
|
|
110
|
+
const result = out.spin('test')
|
|
111
|
+
out.stop()
|
|
112
|
+
expect(result).toBe(out)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('stop()', () => {
|
|
117
|
+
it('should stop spinner and clear line', () => {
|
|
118
|
+
vi.useFakeTimers()
|
|
119
|
+
|
|
120
|
+
out.spin('loading')
|
|
121
|
+
vi.advanceTimersByTime(100)
|
|
122
|
+
|
|
123
|
+
stdoutWriteSpy.mockClear()
|
|
124
|
+
out.stop()
|
|
125
|
+
|
|
126
|
+
// Should write clearing spaces
|
|
127
|
+
expect(stdoutWriteSpy).toHaveBeenCalled()
|
|
128
|
+
|
|
129
|
+
vi.useRealTimers()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should be safe to call multiple times', () => {
|
|
133
|
+
expect(() => {
|
|
134
|
+
out.stop()
|
|
135
|
+
out.stop()
|
|
136
|
+
out.stop()
|
|
137
|
+
}).not.toThrow()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should return self for chaining', () => {
|
|
141
|
+
const result = out.stop()
|
|
142
|
+
expect(result).toBe(out)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('edge cases', () => {
|
|
147
|
+
it('should handle empty messages', () => {
|
|
148
|
+
expect(() => out.done('')).not.toThrow()
|
|
149
|
+
expect(() => out.fail('')).not.toThrow()
|
|
150
|
+
expect(() => out.warn('')).not.toThrow()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should handle null/undefined messages', () => {
|
|
154
|
+
expect(() => out.done(null)).not.toThrow()
|
|
155
|
+
expect(() => out.done(undefined)).not.toThrow()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should handle messages with special characters', () => {
|
|
159
|
+
out.done('test with émojis 🎉 and spëcial çhars')
|
|
160
|
+
expect(consoleLogSpy).toHaveBeenCalled()
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
})
|