prjct-cli 0.10.0 → 0.10.2
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 +31 -0
- package/core/__tests__/agentic/memory-system.test.js +263 -0
- package/core/__tests__/agentic/plan-mode.test.js +336 -0
- package/core/agentic/chain-of-thought.js +578 -0
- package/core/agentic/command-executor.js +238 -4
- package/core/agentic/context-builder.js +208 -8
- package/core/agentic/ground-truth.js +591 -0
- package/core/agentic/loop-detector.js +406 -0
- package/core/agentic/memory-system.js +850 -0
- package/core/agentic/parallel-tools.js +366 -0
- package/core/agentic/plan-mode.js +572 -0
- package/core/agentic/prompt-builder.js +76 -1
- package/core/agentic/response-templates.js +290 -0
- package/core/agentic/semantic-compression.js +517 -0
- package/core/agentic/think-blocks.js +657 -0
- package/core/agentic/tool-registry.js +32 -0
- package/core/agentic/validation-rules.js +380 -0
- package/core/command-registry.js +48 -0
- package/core/commands.js +43 -1
- package/core/context-sync.js +183 -0
- package/package.json +7 -15
- package/templates/commands/done.md +7 -0
- package/templates/commands/feature.md +8 -0
- package/templates/commands/ship.md +8 -0
- package/templates/commands/spec.md +128 -0
- package/templates/global/CLAUDE.md +17 -0
- package/core/__tests__/agentic/agent-router.test.js +0 -398
- package/core/__tests__/agentic/command-executor.test.js +0 -223
- package/core/__tests__/agentic/context-builder.test.js +0 -160
- package/core/__tests__/agentic/context-filter.test.js +0 -494
- package/core/__tests__/agentic/prompt-builder.test.js +0 -204
- package/core/__tests__/agentic/template-loader.test.js +0 -164
- package/core/__tests__/agentic/tool-registry.test.js +0 -243
- package/core/__tests__/domain/agent-generator.test.js +0 -289
- package/core/__tests__/domain/agent-loader.test.js +0 -179
- package/core/__tests__/domain/analyzer.test.js +0 -324
- package/core/__tests__/infrastructure/author-detector.test.js +0 -103
- package/core/__tests__/infrastructure/config-manager.test.js +0 -454
- package/core/__tests__/infrastructure/path-manager.test.js +0 -412
- package/core/__tests__/setup.test.js +0 -15
- package/core/__tests__/utils/date-helper.test.js +0 -169
- package/core/__tests__/utils/file-helper.test.js +0 -258
- package/core/__tests__/utils/jsonl-helper.test.js +0 -387
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { createRequire } from 'module'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import fs from 'fs/promises'
|
|
5
|
-
import os from 'os'
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url)
|
|
8
|
-
|
|
9
|
-
describe('Path Manager', () => {
|
|
10
|
-
let pathManager
|
|
11
|
-
let testProjectPath
|
|
12
|
-
let tempDir
|
|
13
|
-
|
|
14
|
-
beforeEach(async () => {
|
|
15
|
-
pathManager = require('../../infrastructure/path-manager.js')
|
|
16
|
-
|
|
17
|
-
// Create temporary test directory
|
|
18
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-test-'))
|
|
19
|
-
testProjectPath = tempDir
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
afterEach(async () => {
|
|
23
|
-
// Cleanup temp directory
|
|
24
|
-
if (tempDir) {
|
|
25
|
-
try {
|
|
26
|
-
await fs.rm(tempDir, { recursive: true, force: true })
|
|
27
|
-
} catch (error) {
|
|
28
|
-
// Ignore cleanup errors
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('generateProjectId()', () => {
|
|
34
|
-
it('should generate consistent ID for same path', () => {
|
|
35
|
-
const id1 = pathManager.generateProjectId(testProjectPath)
|
|
36
|
-
const id2 = pathManager.generateProjectId(testProjectPath)
|
|
37
|
-
|
|
38
|
-
expect(id1).toBe(id2)
|
|
39
|
-
expect(id1.length).toBe(12)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('should generate different IDs for different paths', () => {
|
|
43
|
-
const id1 = pathManager.generateProjectId('/path/one')
|
|
44
|
-
const id2 = pathManager.generateProjectId('/path/two')
|
|
45
|
-
|
|
46
|
-
expect(id1).not.toBe(id2)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should use absolute path for consistency', () => {
|
|
50
|
-
const relativePath = './test'
|
|
51
|
-
const absolutePath = path.resolve(relativePath)
|
|
52
|
-
|
|
53
|
-
const id1 = pathManager.generateProjectId(relativePath)
|
|
54
|
-
const id2 = pathManager.generateProjectId(absolutePath)
|
|
55
|
-
|
|
56
|
-
expect(id1).toBe(id2)
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('getGlobalBasePath()', () => {
|
|
61
|
-
it('should return path to .prjct-cli directory', () => {
|
|
62
|
-
const basePath = pathManager.getGlobalBasePath()
|
|
63
|
-
|
|
64
|
-
expect(basePath).toContain('.prjct-cli')
|
|
65
|
-
expect(basePath).toContain(os.homedir())
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
describe('getGlobalProjectPath()', () => {
|
|
70
|
-
it('should return path to project directory', () => {
|
|
71
|
-
const projectId = 'test-project-123'
|
|
72
|
-
const projectPath = pathManager.getGlobalProjectPath(projectId)
|
|
73
|
-
|
|
74
|
-
expect(projectPath).toContain(projectId)
|
|
75
|
-
expect(projectPath).toContain('.prjct-cli')
|
|
76
|
-
expect(projectPath).toContain('projects')
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
describe('getLocalConfigPath()', () => {
|
|
81
|
-
it('should return path to local config file', () => {
|
|
82
|
-
const configPath = pathManager.getLocalConfigPath(testProjectPath)
|
|
83
|
-
|
|
84
|
-
expect(configPath).toContain('.prjct')
|
|
85
|
-
expect(configPath).toContain('prjct.config.json')
|
|
86
|
-
expect(configPath).toContain(testProjectPath)
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('getGlobalProjectConfigPath()', () => {
|
|
91
|
-
it('should return path to global config file', () => {
|
|
92
|
-
const projectId = 'test-123'
|
|
93
|
-
const configPath = pathManager.getGlobalProjectConfigPath(projectId)
|
|
94
|
-
|
|
95
|
-
expect(configPath).toContain(projectId)
|
|
96
|
-
expect(configPath).toContain('project.json')
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('getLegacyPrjctPath()', () => {
|
|
101
|
-
it('should return path to legacy .prjct directory', () => {
|
|
102
|
-
const legacyPath = pathManager.getLegacyPrjctPath(testProjectPath)
|
|
103
|
-
|
|
104
|
-
expect(legacyPath).toContain('.prjct')
|
|
105
|
-
expect(legacyPath).toContain(testProjectPath)
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('hasLegacyStructure()', () => {
|
|
110
|
-
it('should return true if legacy directory exists', async () => {
|
|
111
|
-
const legacyPath = path.join(testProjectPath, '.prjct')
|
|
112
|
-
await fs.mkdir(legacyPath, { recursive: true })
|
|
113
|
-
|
|
114
|
-
const hasLegacy = await pathManager.hasLegacyStructure(testProjectPath)
|
|
115
|
-
|
|
116
|
-
expect(hasLegacy).toBe(true)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('should return false if legacy directory does not exist', async () => {
|
|
120
|
-
const hasLegacy = await pathManager.hasLegacyStructure(testProjectPath)
|
|
121
|
-
|
|
122
|
-
expect(hasLegacy).toBe(false)
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
describe('hasConfig()', () => {
|
|
127
|
-
it('should return true if config file exists', async () => {
|
|
128
|
-
const configPath = pathManager.getLocalConfigPath(testProjectPath)
|
|
129
|
-
await fs.mkdir(path.dirname(configPath), { recursive: true })
|
|
130
|
-
await fs.writeFile(configPath, '{}')
|
|
131
|
-
|
|
132
|
-
const hasConfig = await pathManager.hasConfig(testProjectPath)
|
|
133
|
-
|
|
134
|
-
expect(hasConfig).toBe(true)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('should return false if config file does not exist', async () => {
|
|
138
|
-
const hasConfig = await pathManager.hasConfig(testProjectPath)
|
|
139
|
-
|
|
140
|
-
expect(hasConfig).toBe(false)
|
|
141
|
-
})
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
describe('ensureGlobalStructure()', () => {
|
|
145
|
-
it('should create global directory structure', async () => {
|
|
146
|
-
await pathManager.ensureGlobalStructure()
|
|
147
|
-
|
|
148
|
-
const basePath = pathManager.getGlobalBasePath()
|
|
149
|
-
const exists = await fs.access(basePath).then(() => true).catch(() => false)
|
|
150
|
-
|
|
151
|
-
expect(exists).toBe(true)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('should create projects directory', async () => {
|
|
155
|
-
await pathManager.ensureGlobalStructure()
|
|
156
|
-
|
|
157
|
-
const projectsDir = path.join(pathManager.getGlobalBasePath(), 'projects')
|
|
158
|
-
const exists = await fs.access(projectsDir).then(() => true).catch(() => false)
|
|
159
|
-
|
|
160
|
-
expect(exists).toBe(true)
|
|
161
|
-
})
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
describe('ensureProjectStructure()', () => {
|
|
165
|
-
it('should create project directory structure', async () => {
|
|
166
|
-
const projectId = 'test-structure-123'
|
|
167
|
-
const projectPath = await pathManager.ensureProjectStructure(projectId)
|
|
168
|
-
|
|
169
|
-
expect(projectPath).toBeDefined()
|
|
170
|
-
expect(projectPath).toContain(projectId)
|
|
171
|
-
|
|
172
|
-
// Check that layers were created
|
|
173
|
-
const corePath = path.join(projectPath, 'core')
|
|
174
|
-
const exists = await fs.access(corePath).then(() => true).catch(() => false)
|
|
175
|
-
expect(exists).toBe(true)
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('should create all required layers', async () => {
|
|
179
|
-
const projectId = 'test-layers-123'
|
|
180
|
-
const projectPath = await pathManager.ensureProjectStructure(projectId)
|
|
181
|
-
|
|
182
|
-
const layers = ['core', 'progress', 'planning', 'analysis', 'memory']
|
|
183
|
-
for (const layer of layers) {
|
|
184
|
-
const layerPath = path.join(projectPath, layer)
|
|
185
|
-
const exists = await fs.access(layerPath).then(() => true).catch(() => false)
|
|
186
|
-
expect(exists).toBe(true)
|
|
187
|
-
}
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('should create nested directories', async () => {
|
|
191
|
-
const projectId = 'test-nested-123'
|
|
192
|
-
const projectPath = await pathManager.ensureProjectStructure(projectId)
|
|
193
|
-
|
|
194
|
-
const tasksPath = path.join(projectPath, 'planning', 'tasks')
|
|
195
|
-
const exists = await fs.access(tasksPath).then(() => true).catch(() => false)
|
|
196
|
-
expect(exists).toBe(true)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
describe('getSessionPath()', () => {
|
|
201
|
-
it('should return path with date structure', () => {
|
|
202
|
-
const projectId = 'test-session-123'
|
|
203
|
-
const date = new Date('2025-10-15')
|
|
204
|
-
const sessionPath = pathManager.getSessionPath(projectId, date)
|
|
205
|
-
|
|
206
|
-
expect(sessionPath).toContain('2025')
|
|
207
|
-
expect(sessionPath).toContain('10')
|
|
208
|
-
// Day may vary due to timezone, just check it contains a day
|
|
209
|
-
expect(sessionPath).toMatch(/\d{2}/)
|
|
210
|
-
expect(sessionPath).toContain('sessions')
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('should use today if no date provided', () => {
|
|
214
|
-
const projectId = 'test-session-456'
|
|
215
|
-
const sessionPath = pathManager.getSessionPath(projectId)
|
|
216
|
-
|
|
217
|
-
expect(sessionPath).toContain('sessions')
|
|
218
|
-
expect(sessionPath).toContain(projectId)
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
describe('getCurrentSessionPath()', () => {
|
|
223
|
-
it('should return path for today', () => {
|
|
224
|
-
const projectId = 'test-current-123'
|
|
225
|
-
const sessionPath = pathManager.getCurrentSessionPath(projectId)
|
|
226
|
-
|
|
227
|
-
expect(sessionPath).toContain('sessions')
|
|
228
|
-
expect(sessionPath).toContain(projectId)
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
describe('ensureSessionPath()', () => {
|
|
233
|
-
it('should create session directory', async () => {
|
|
234
|
-
const projectId = 'test-ensure-session-123'
|
|
235
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
236
|
-
|
|
237
|
-
const date = new Date('2025-10-15')
|
|
238
|
-
const sessionPath = await pathManager.ensureSessionPath(projectId, date)
|
|
239
|
-
|
|
240
|
-
expect(sessionPath).toBeDefined()
|
|
241
|
-
const exists = await fs.access(sessionPath).then(() => true).catch(() => false)
|
|
242
|
-
expect(exists).toBe(true)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('should use today if no date provided', async () => {
|
|
246
|
-
const projectId = 'test-ensure-today-123'
|
|
247
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
248
|
-
|
|
249
|
-
const sessionPath = await pathManager.ensureSessionPath(projectId)
|
|
250
|
-
|
|
251
|
-
expect(sessionPath).toBeDefined()
|
|
252
|
-
const exists = await fs.access(sessionPath).then(() => true).catch(() => false)
|
|
253
|
-
expect(exists).toBe(true)
|
|
254
|
-
})
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
describe('listSessions()', () => {
|
|
258
|
-
it('should return empty array for non-existent sessions', async () => {
|
|
259
|
-
const projectId = 'test-list-empty-123'
|
|
260
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
261
|
-
|
|
262
|
-
const sessions = await pathManager.listSessions(projectId)
|
|
263
|
-
|
|
264
|
-
expect(sessions).toEqual([])
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
it('should list sessions when they exist', async () => {
|
|
268
|
-
const projectId = 'test-list-sessions-123'
|
|
269
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
270
|
-
|
|
271
|
-
// Create a session directory
|
|
272
|
-
const date = new Date('2025-10-15')
|
|
273
|
-
await pathManager.ensureSessionPath(projectId, date)
|
|
274
|
-
|
|
275
|
-
const sessions = await pathManager.listSessions(projectId)
|
|
276
|
-
|
|
277
|
-
expect(sessions.length).toBeGreaterThan(0)
|
|
278
|
-
expect(sessions[0].year).toBe('2025')
|
|
279
|
-
expect(sessions[0].month).toBe('10')
|
|
280
|
-
// Day may vary due to date parsing, just check it's a valid day string
|
|
281
|
-
expect(sessions[0].day).toMatch(/^\d{2}$/)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('should filter by year', async () => {
|
|
285
|
-
const projectId = 'test-filter-year-123'
|
|
286
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
287
|
-
|
|
288
|
-
await pathManager.ensureSessionPath(projectId, new Date('2025-10-15'))
|
|
289
|
-
await pathManager.ensureSessionPath(projectId, new Date('2024-10-15'))
|
|
290
|
-
|
|
291
|
-
const sessions2025 = await pathManager.listSessions(projectId, '2025')
|
|
292
|
-
const sessions2024 = await pathManager.listSessions(projectId, '2024')
|
|
293
|
-
|
|
294
|
-
expect(sessions2025.every(s => s.year === '2025')).toBe(true)
|
|
295
|
-
expect(sessions2024.every(s => s.year === '2024')).toBe(true)
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
it('should filter by month', async () => {
|
|
299
|
-
const projectId = 'test-filter-month-123'
|
|
300
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
301
|
-
|
|
302
|
-
await pathManager.ensureSessionPath(projectId, new Date('2025-10-15'))
|
|
303
|
-
await pathManager.ensureSessionPath(projectId, new Date('2025-11-15'))
|
|
304
|
-
|
|
305
|
-
const sessionsOct = await pathManager.listSessions(projectId, '2025', 10)
|
|
306
|
-
const sessionsNov = await pathManager.listSessions(projectId, '2025', 11)
|
|
307
|
-
|
|
308
|
-
expect(sessionsOct.every(s => s.month === '10')).toBe(true)
|
|
309
|
-
expect(sessionsNov.every(s => s.month === '11')).toBe(true)
|
|
310
|
-
})
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
describe('getSessionsInRange()', () => {
|
|
314
|
-
it('should return sessions within date range', async () => {
|
|
315
|
-
const projectId = 'test-range-123'
|
|
316
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
317
|
-
|
|
318
|
-
await pathManager.ensureSessionPath(projectId, new Date('2025-10-15'))
|
|
319
|
-
await pathManager.ensureSessionPath(projectId, new Date('2025-10-20'))
|
|
320
|
-
await pathManager.ensureSessionPath(projectId, new Date('2025-11-01'))
|
|
321
|
-
|
|
322
|
-
const fromDate = new Date('2025-10-10')
|
|
323
|
-
const toDate = new Date('2025-10-25')
|
|
324
|
-
|
|
325
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate, toDate)
|
|
326
|
-
|
|
327
|
-
expect(sessions.length).toBeGreaterThan(0)
|
|
328
|
-
expect(sessions.every(s => s.date >= fromDate && s.date <= toDate)).toBe(true)
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
it('should use today as default end date', async () => {
|
|
332
|
-
const projectId = 'test-range-today-123'
|
|
333
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
334
|
-
|
|
335
|
-
await pathManager.ensureSessionPath(projectId, new Date())
|
|
336
|
-
|
|
337
|
-
const fromDate = new Date()
|
|
338
|
-
fromDate.setDate(fromDate.getDate() - 1)
|
|
339
|
-
|
|
340
|
-
const sessions = await pathManager.getSessionsInRange(projectId, fromDate)
|
|
341
|
-
|
|
342
|
-
expect(sessions.length).toBeGreaterThan(0)
|
|
343
|
-
})
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
describe('getFilePath()', () => {
|
|
347
|
-
it('should return path to file in layer', () => {
|
|
348
|
-
const projectId = 'test-file-123'
|
|
349
|
-
const filePath = pathManager.getFilePath(projectId, 'core', 'now.md')
|
|
350
|
-
|
|
351
|
-
expect(filePath).toContain(projectId)
|
|
352
|
-
expect(filePath).toContain('core')
|
|
353
|
-
expect(filePath).toContain('now.md')
|
|
354
|
-
})
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
describe('listProjects()', () => {
|
|
358
|
-
it('should return empty array when no projects exist', async () => {
|
|
359
|
-
const projects = await pathManager.listProjects()
|
|
360
|
-
|
|
361
|
-
// May have existing projects, so just check it's an array
|
|
362
|
-
expect(Array.isArray(projects)).toBe(true)
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
it('should list existing projects', async () => {
|
|
366
|
-
const projectId = 'test-list-123'
|
|
367
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
368
|
-
|
|
369
|
-
const projects = await pathManager.listProjects()
|
|
370
|
-
|
|
371
|
-
expect(projects).toContain(projectId)
|
|
372
|
-
})
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
describe('projectExists()', () => {
|
|
376
|
-
it('should return true for existing project', async () => {
|
|
377
|
-
const projectId = 'test-exists-123'
|
|
378
|
-
await pathManager.ensureProjectStructure(projectId)
|
|
379
|
-
|
|
380
|
-
const exists = await pathManager.projectExists(projectId)
|
|
381
|
-
|
|
382
|
-
expect(exists).toBe(true)
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
it('should return false for non-existent project', async () => {
|
|
386
|
-
const exists = await pathManager.projectExists('non-existent-project-id')
|
|
387
|
-
|
|
388
|
-
expect(exists).toBe(false)
|
|
389
|
-
})
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
describe('getDisplayPath()', () => {
|
|
393
|
-
it('should replace home directory with ~', () => {
|
|
394
|
-
const homeDir = os.homedir()
|
|
395
|
-
const testPath = path.join(homeDir, '.prjct-cli', 'projects', 'test')
|
|
396
|
-
|
|
397
|
-
const displayPath = pathManager.getDisplayPath(testPath)
|
|
398
|
-
|
|
399
|
-
expect(displayPath).toContain('~')
|
|
400
|
-
expect(displayPath).not.toContain(homeDir)
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
it('should return original path if not in home directory', () => {
|
|
404
|
-
const testPath = '/some/other/path'
|
|
405
|
-
|
|
406
|
-
const displayPath = pathManager.getDisplayPath(testPath)
|
|
407
|
-
|
|
408
|
-
expect(displayPath).toBe(testPath)
|
|
409
|
-
})
|
|
410
|
-
})
|
|
411
|
-
})
|
|
412
|
-
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
|
|
3
|
-
describe('Vitest Setup', () => {
|
|
4
|
-
it('should pass basic assertion', () => {
|
|
5
|
-
expect(true).toBe(true)
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
it('should handle numbers', () => {
|
|
9
|
-
expect(2 + 2).toBe(4)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('should handle strings', () => {
|
|
13
|
-
expect('prjct-cli').toContain('prjct')
|
|
14
|
-
})
|
|
15
|
-
})
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import * as dateHelper from '../../utils/date-helper.js'
|
|
3
|
-
|
|
4
|
-
describe('Date Helper', () => {
|
|
5
|
-
describe('formatDate()', () => {
|
|
6
|
-
it('should format date to YYYY-MM-DD', () => {
|
|
7
|
-
const date = new Date(2025, 9, 4, 12, 0, 0) // October 4, 2025
|
|
8
|
-
const formatted = dateHelper.formatDate(date)
|
|
9
|
-
|
|
10
|
-
expect(formatted).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('should pad single digit month', () => {
|
|
14
|
-
const date = new Date(2025, 2, 15) // March 15, 2025
|
|
15
|
-
const formatted = dateHelper.formatDate(date)
|
|
16
|
-
|
|
17
|
-
expect(formatted).toContain('-03-')
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should pad single digit day', () => {
|
|
21
|
-
const date = new Date(2025, 9, 5) // October 5, 2025
|
|
22
|
-
const formatted = dateHelper.formatDate(date)
|
|
23
|
-
|
|
24
|
-
expect(formatted).toContain('-05')
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('should handle edge case dates', () => {
|
|
28
|
-
const date = new Date(2025, 0, 1) // January 1, 2025
|
|
29
|
-
const formatted = dateHelper.formatDate(date)
|
|
30
|
-
|
|
31
|
-
expect(formatted).toBe('2025-01-01')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('should handle end of year', () => {
|
|
35
|
-
const date = new Date(2025, 11, 31) // December 31, 2025
|
|
36
|
-
const formatted = dateHelper.formatDate(date)
|
|
37
|
-
|
|
38
|
-
expect(formatted).toBe('2025-12-31')
|
|
39
|
-
})
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
describe('formatMonth()', () => {
|
|
43
|
-
it('should format date to YYYY-MM', () => {
|
|
44
|
-
const date = new Date('2025-10-04')
|
|
45
|
-
const formatted = dateHelper.formatMonth(date)
|
|
46
|
-
|
|
47
|
-
expect(formatted).toMatch(/^\d{4}-\d{2}$/)
|
|
48
|
-
expect(formatted).toBe('2025-10')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('should pad single digit month', () => {
|
|
52
|
-
const date = new Date('2025-03-15')
|
|
53
|
-
const formatted = dateHelper.formatMonth(date)
|
|
54
|
-
|
|
55
|
-
expect(formatted).toBe('2025-03')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should handle January', () => {
|
|
59
|
-
const date = new Date('2025-01-15')
|
|
60
|
-
const formatted = dateHelper.formatMonth(date)
|
|
61
|
-
|
|
62
|
-
expect(formatted).toBe('2025-01')
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should handle December', () => {
|
|
66
|
-
const date = new Date('2025-12-25')
|
|
67
|
-
const formatted = dateHelper.formatMonth(date)
|
|
68
|
-
|
|
69
|
-
expect(formatted).toBe('2025-12')
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
describe('getTodayKey()', () => {
|
|
74
|
-
it('should return today in YYYY-MM-DD format', () => {
|
|
75
|
-
const today = dateHelper.getTodayKey()
|
|
76
|
-
|
|
77
|
-
expect(today).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('should match formatDate for current date', () => {
|
|
81
|
-
const today = dateHelper.getTodayKey()
|
|
82
|
-
const now = dateHelper.formatDate(new Date())
|
|
83
|
-
|
|
84
|
-
expect(today).toBe(now)
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
describe('getDateKey()', () => {
|
|
89
|
-
it('should return date key in YYYY-MM-DD format', () => {
|
|
90
|
-
const date = new Date(2025, 9, 4) // October 4, 2025
|
|
91
|
-
const key = dateHelper.getDateKey(date)
|
|
92
|
-
|
|
93
|
-
expect(key).toBe('2025-10-04')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('should be alias for formatDate', () => {
|
|
97
|
-
const date = new Date(2025, 9, 4) // October 4, 2025
|
|
98
|
-
|
|
99
|
-
expect(dateHelper.getDateKey(date)).toBe(dateHelper.formatDate(date))
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
describe('getTimestamp()', () => {
|
|
104
|
-
it('should return ISO timestamp', () => {
|
|
105
|
-
const timestamp = dateHelper.getTimestamp()
|
|
106
|
-
|
|
107
|
-
expect(timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('should be valid date string', () => {
|
|
111
|
-
const timestamp = dateHelper.getTimestamp()
|
|
112
|
-
const date = new Date(timestamp)
|
|
113
|
-
|
|
114
|
-
expect(date.toString()).not.toBe('Invalid Date')
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
describe('calculateDuration()', () => {
|
|
119
|
-
it('should calculate duration in minutes', () => {
|
|
120
|
-
const start = new Date('2025-10-04T10:00:00Z')
|
|
121
|
-
const end = new Date('2025-10-04T10:30:00Z')
|
|
122
|
-
const duration = dateHelper.calculateDuration(start, end)
|
|
123
|
-
|
|
124
|
-
expect(duration).toContain('30m')
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('should calculate duration in hours and minutes', () => {
|
|
128
|
-
const start = new Date('2025-10-04T10:00:00Z')
|
|
129
|
-
const end = new Date('2025-10-04T12:30:00Z')
|
|
130
|
-
const duration = dateHelper.calculateDuration(start, end)
|
|
131
|
-
|
|
132
|
-
expect(duration).toContain('2h')
|
|
133
|
-
expect(duration).toContain('30m')
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('should handle exact hours', () => {
|
|
137
|
-
const start = new Date('2025-10-04T10:00:00Z')
|
|
138
|
-
const end = new Date('2025-10-04T13:00:00Z')
|
|
139
|
-
const duration = dateHelper.calculateDuration(start, end)
|
|
140
|
-
|
|
141
|
-
expect(duration).toContain('3h')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('should handle less than 1 minute', () => {
|
|
145
|
-
const start = new Date('2025-10-04T10:00:00Z')
|
|
146
|
-
const end = new Date('2025-10-04T10:00:30Z')
|
|
147
|
-
const duration = dateHelper.calculateDuration(start, end)
|
|
148
|
-
|
|
149
|
-
expect(duration).toContain('30s')
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('Edge Cases', () => {
|
|
154
|
-
it('should handle leap year', () => {
|
|
155
|
-
const date = new Date(2024, 1, 29) // February 29, 2024
|
|
156
|
-
const formatted = dateHelper.formatDate(date)
|
|
157
|
-
|
|
158
|
-
expect(formatted).toBe('2024-02-29')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('should handle different years', () => {
|
|
162
|
-
const date2024 = new Date(2024, 9, 4) // October 4, 2024
|
|
163
|
-
const date2025 = new Date(2025, 9, 4) // October 4, 2025
|
|
164
|
-
|
|
165
|
-
expect(dateHelper.formatDate(date2024)).toBe('2024-10-04')
|
|
166
|
-
expect(dateHelper.formatDate(date2025)).toBe('2025-10-04')
|
|
167
|
-
})
|
|
168
|
-
})
|
|
169
|
-
})
|