@pep/term-deck 1.0.13 → 1.0.15

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 (36) hide show
  1. package/dist/bin/term-deck.d.ts +1 -0
  2. package/dist/bin/term-deck.js +1720 -0
  3. package/dist/bin/term-deck.js.map +1 -0
  4. package/dist/index.d.ts +670 -0
  5. package/dist/index.js +159 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +16 -13
  8. package/bin/term-deck.ts +0 -45
  9. package/src/cli/__tests__/errors.test.ts +0 -201
  10. package/src/cli/__tests__/help.test.ts +0 -157
  11. package/src/cli/__tests__/init.test.ts +0 -110
  12. package/src/cli/commands/export.ts +0 -33
  13. package/src/cli/commands/init.ts +0 -125
  14. package/src/cli/commands/present.ts +0 -29
  15. package/src/cli/errors.ts +0 -77
  16. package/src/core/__tests__/slide.test.ts +0 -1759
  17. package/src/core/__tests__/theme.test.ts +0 -1103
  18. package/src/core/slide.ts +0 -509
  19. package/src/core/theme.ts +0 -388
  20. package/src/export/__tests__/recorder.test.ts +0 -566
  21. package/src/export/recorder.ts +0 -639
  22. package/src/index.ts +0 -36
  23. package/src/presenter/__tests__/main.test.ts +0 -244
  24. package/src/presenter/main.ts +0 -658
  25. package/src/renderer/__tests__/screen-extended.test.ts +0 -801
  26. package/src/renderer/__tests__/screen.test.ts +0 -525
  27. package/src/renderer/screen.ts +0 -671
  28. package/src/schemas/__tests__/config.test.ts +0 -429
  29. package/src/schemas/__tests__/slide.test.ts +0 -349
  30. package/src/schemas/__tests__/theme.test.ts +0 -970
  31. package/src/schemas/__tests__/validation.test.ts +0 -256
  32. package/src/schemas/config.ts +0 -58
  33. package/src/schemas/slide.ts +0 -56
  34. package/src/schemas/theme.ts +0 -203
  35. package/src/schemas/validation.ts +0 -64
  36. package/src/themes/matrix/index.ts +0 -53
@@ -1,525 +0,0 @@
1
- import { describe, it, expect, mock } from 'bun:test'
2
- import {
3
- createScreen,
4
- createRenderer,
5
- destroyRenderer,
6
- generateBigText,
7
- generateMultiLineBigText,
8
- lineByLineReveal,
9
- glitchLine,
10
- getWindowColor,
11
- createWindow,
12
- clearWindows,
13
- renderMatrixRain,
14
- initMatrixRain,
15
- applyTransition,
16
- renderSlide,
17
- } from '../screen'
18
- import { DEFAULT_THEME } from '../../schemas/theme'
19
- import type { Slide } from '../../schemas/slide'
20
-
21
- describe('createScreen', () => {
22
- it('creates a blessed screen with correct configuration', () => {
23
- const screen = createScreen()
24
-
25
- expect(screen).toBeDefined()
26
- // Clean up
27
- screen.destroy()
28
- })
29
-
30
- it('accepts custom title', () => {
31
- const title = 'Custom Title'
32
- const screen = createScreen(title)
33
-
34
- expect(screen).toBeDefined()
35
- // Clean up
36
- screen.destroy()
37
- })
38
-
39
- it('uses default title when none provided', () => {
40
- const screen = createScreen()
41
-
42
- expect(screen).toBeDefined()
43
- // Clean up
44
- screen.destroy()
45
- })
46
- })
47
-
48
- describe('createRenderer', () => {
49
- it('initializes renderer with all components', () => {
50
- const renderer = createRenderer(DEFAULT_THEME)
51
-
52
- expect(renderer).toBeDefined()
53
- expect(renderer.screen).toBeDefined()
54
- expect(renderer.matrixBox).toBeDefined()
55
- expect(renderer.windowStack).toEqual([])
56
- expect(renderer.theme).toBe(DEFAULT_THEME)
57
- expect(Array.isArray(renderer.matrixDrops)).toBe(true)
58
- expect(renderer.matrixDrops.length).toBeGreaterThan(0)
59
- expect(renderer.matrixInterval).toBeDefined()
60
-
61
- // Clean up
62
- destroyRenderer(renderer)
63
- })
64
-
65
- it('attaches matrix box to screen', () => {
66
- const renderer = createRenderer(DEFAULT_THEME)
67
-
68
- expect(renderer.matrixBox.parent).toBe(renderer.screen)
69
-
70
- // Clean up
71
- destroyRenderer(renderer)
72
- })
73
-
74
- it('initializes matrix rain on creation', () => {
75
- const renderer = createRenderer(DEFAULT_THEME)
76
-
77
- // Matrix rain should be initialized (even if empty for now)
78
- expect(renderer.matrixDrops).toBeDefined()
79
- expect(Array.isArray(renderer.matrixDrops)).toBe(true)
80
-
81
- // Clean up
82
- destroyRenderer(renderer)
83
- })
84
- })
85
-
86
- describe('destroyRenderer', () => {
87
- it('clears matrix interval if running', () => {
88
- const renderer = createRenderer(DEFAULT_THEME)
89
-
90
- // Set up a mock interval
91
- renderer.matrixInterval = setInterval(() => {}, 100) as NodeJS.Timer
92
-
93
- destroyRenderer(renderer)
94
-
95
- expect(renderer.matrixInterval).toBeNull()
96
- })
97
-
98
- it('handles null matrix interval gracefully', () => {
99
- const renderer = createRenderer(DEFAULT_THEME)
100
-
101
- // Clear the interval so it becomes null
102
- if (renderer.matrixInterval) {
103
- clearInterval(renderer.matrixInterval)
104
- renderer.matrixInterval = null
105
- }
106
-
107
- // Should not throw when interval is null
108
- expect(() => destroyRenderer(renderer)).not.toThrow()
109
- })
110
-
111
- it('destroys all windows in the stack', () => {
112
- const renderer = createRenderer(DEFAULT_THEME)
113
-
114
- // Create mock windows
115
- const mockWindow1 = {
116
- destroy: mock(() => {}),
117
- }
118
- const mockWindow2 = {
119
- destroy: mock(() => {}),
120
- }
121
-
122
- renderer.windowStack = [mockWindow1 as any, mockWindow2 as any]
123
-
124
- destroyRenderer(renderer)
125
-
126
- expect(mockWindow1.destroy).toHaveBeenCalledTimes(1)
127
- expect(mockWindow2.destroy).toHaveBeenCalledTimes(1)
128
- expect(renderer.windowStack).toEqual([])
129
- })
130
-
131
- it('empties the window stack after destroying windows', () => {
132
- const renderer = createRenderer(DEFAULT_THEME)
133
-
134
- // Add mock windows
135
- renderer.windowStack = [
136
- { destroy: mock(() => {}) } as any,
137
- { destroy: mock(() => {}) } as any,
138
- { destroy: mock(() => {}) } as any,
139
- ]
140
-
141
- destroyRenderer(renderer)
142
-
143
- expect(renderer.windowStack).toEqual([])
144
- })
145
-
146
- it('destroys the blessed screen', () => {
147
- const renderer = createRenderer(DEFAULT_THEME)
148
-
149
- // Mock the screen destroy method
150
- const destroySpy = mock(() => {})
151
- renderer.screen.destroy = destroySpy
152
-
153
- destroyRenderer(renderer)
154
-
155
- expect(destroySpy).toHaveBeenCalledTimes(1)
156
- })
157
-
158
- it('performs full cleanup in correct order', () => {
159
- const renderer = createRenderer(DEFAULT_THEME)
160
-
161
- // Set up interval and windows
162
- renderer.matrixInterval = setInterval(() => {}, 100) as NodeJS.Timer
163
- const mockWindow = { destroy: mock(() => {}) }
164
- renderer.windowStack = [mockWindow as any]
165
- const screenDestroySpy = mock(() => {})
166
- renderer.screen.destroy = screenDestroySpy
167
-
168
- destroyRenderer(renderer)
169
-
170
- // Verify all cleanup steps occurred
171
- expect(renderer.matrixInterval).toBeNull()
172
- expect(mockWindow.destroy).toHaveBeenCalled()
173
- expect(renderer.windowStack).toEqual([])
174
- expect(screenDestroySpy).toHaveBeenCalled()
175
- })
176
-
177
- it('handles empty window stack', () => {
178
- const renderer = createRenderer(DEFAULT_THEME)
179
-
180
- expect(renderer.windowStack).toEqual([])
181
-
182
- // Should not throw with empty stack
183
- expect(() => destroyRenderer(renderer)).not.toThrow()
184
- })
185
- })
186
-
187
- describe('generateBigText', () => {
188
- it('generates ASCII art text successfully', async () => {
189
- const text = 'HELLO'
190
- const gradientColors = ['#00cc66', '#ff6600']
191
-
192
- const result = await generateBigText(text, gradientColors)
193
-
194
- expect(result).toBeDefined()
195
- expect(typeof result).toBe('string')
196
- expect(result.length).toBeGreaterThan(0)
197
- })
198
-
199
- it('applies gradient colors to ASCII art', async () => {
200
- const text = 'TEST'
201
- const gradientColors = ['#00cc66', '#ff6600']
202
-
203
- const result = await generateBigText(text, gradientColors)
204
-
205
- // The gradient library adds ANSI color codes
206
- expect(result).toBeDefined()
207
- expect(result.length).toBeGreaterThan(text.length)
208
- })
209
-
210
- it('uses Standard font by default', async () => {
211
- const text = 'ABC'
212
- const gradientColors = ['#00cc66', '#ff6600']
213
-
214
- const result = await generateBigText(text, gradientColors)
215
-
216
- expect(result).toBeDefined()
217
- // Standard font should produce multi-line ASCII art
218
- expect(result.includes('\n')).toBe(true)
219
- })
220
-
221
- it('accepts custom font parameter', async () => {
222
- const text = 'XYZ'
223
- const gradientColors = ['#00cc66', '#ff6600']
224
- const font = 'Small'
225
-
226
- const result = await generateBigText(text, gradientColors, font)
227
-
228
- expect(result).toBeDefined()
229
- expect(typeof result).toBe('string')
230
- })
231
-
232
- it('handles single character input', async () => {
233
- const text = 'A'
234
- const gradientColors = ['#00cc66', '#ff6600']
235
-
236
- const result = await generateBigText(text, gradientColors)
237
-
238
- expect(result).toBeDefined()
239
- expect(result.length).toBeGreaterThan(0)
240
- })
241
-
242
- it('handles empty string gracefully', async () => {
243
- const text = ''
244
- const gradientColors = ['#00cc66', '#ff6600']
245
-
246
- const result = await generateBigText(text, gradientColors)
247
-
248
- expect(result).toBeDefined()
249
- })
250
- })
251
-
252
- describe('generateMultiLineBigText', () => {
253
- it('generates ASCII art for multiple lines', async () => {
254
- const lines = ['SPEC', 'MACHINE']
255
- const gradientColors = ['#00cc66', '#ff6600']
256
-
257
- const result = await generateMultiLineBigText(lines, gradientColors)
258
-
259
- expect(result).toBeDefined()
260
- expect(typeof result).toBe('string')
261
- expect(result.length).toBeGreaterThan(0)
262
- })
263
-
264
- it('joins lines with newlines', async () => {
265
- const lines = ['LINE1', 'LINE2']
266
- const gradientColors = ['#00cc66', '#ff6600']
267
-
268
- const result = await generateMultiLineBigText(lines, gradientColors)
269
-
270
- // Should have newlines between the ASCII art blocks
271
- const newlineCount = (result.match(/\n/g) || []).length
272
- expect(newlineCount).toBeGreaterThan(0)
273
- })
274
-
275
- it('applies gradient to each line independently', async () => {
276
- const lines = ['A', 'B', 'C']
277
- const gradientColors = ['#00cc66', '#ff6600']
278
-
279
- const result = await generateMultiLineBigText(lines, gradientColors)
280
-
281
- expect(result).toBeDefined()
282
- // Each line should be processed
283
- expect(result.length).toBeGreaterThan(lines.length)
284
- })
285
-
286
- it('handles empty array', async () => {
287
- const lines: string[] = []
288
- const gradientColors = ['#00cc66', '#ff6600']
289
-
290
- const result = await generateMultiLineBigText(lines, gradientColors)
291
-
292
- expect(result).toBeDefined()
293
- expect(result).toBe('')
294
- })
295
-
296
- it('handles single line array', async () => {
297
- const lines = ['ONLY']
298
- const gradientColors = ['#00cc66', '#ff6600']
299
-
300
- const result = await generateMultiLineBigText(lines, gradientColors)
301
-
302
- expect(result).toBeDefined()
303
- expect(typeof result).toBe('string')
304
- })
305
-
306
- it('uses custom font parameter', async () => {
307
- const lines = ['TEST', 'FONT']
308
- const gradientColors = ['#00cc66', '#ff6600']
309
- const font = 'Small'
310
-
311
- const result = await generateMultiLineBigText(lines, gradientColors, font)
312
-
313
- expect(result).toBeDefined()
314
- expect(typeof result).toBe('string')
315
- })
316
-
317
- it('processes all lines in parallel', async () => {
318
- const lines = ['ONE', 'TWO', 'THREE']
319
- const gradientColors = ['#00cc66', '#ff6600']
320
-
321
- const startTime = Date.now()
322
- const result = await generateMultiLineBigText(lines, gradientColors)
323
- const duration = Date.now() - startTime
324
-
325
- expect(result).toBeDefined()
326
- // Processing should be relatively fast due to parallelization
327
- // This is a loose check - mainly verifying it completes
328
- expect(duration).toBeLessThan(5000)
329
- })
330
- })
331
-
332
- describe('glitchLine', () => {
333
- it('animates a single line with glitch effect', async () => {
334
- const renderer = createRenderer(DEFAULT_THEME)
335
- const box = renderer.windowStack[0] || {
336
- setContent: mock(() => {}),
337
- } as any
338
- const screen = renderer.screen
339
- const currentLines = ['Line 1', 'Line 2']
340
- const newLine = 'Line 3'
341
-
342
- await glitchLine(box, screen, currentLines, newLine, 3)
343
-
344
- // Should have called setContent multiple times during animation
345
- expect(box.setContent).toHaveBeenCalled()
346
-
347
- destroyRenderer(renderer)
348
- })
349
-
350
- it('preserves protected characters during glitch', async () => {
351
- const renderer = createRenderer(DEFAULT_THEME)
352
- const contentCalls: string[] = []
353
- const box = {
354
- setContent: mock((content: string) => {
355
- contentCalls.push(content)
356
- }),
357
- } as any
358
- const screen = renderer.screen
359
- const currentLines: string[] = []
360
- const newLine = 'Hello, World!'
361
-
362
- await glitchLine(box, screen, currentLines, newLine, 5)
363
-
364
- // Check that spaces and punctuation were preserved in all calls
365
- for (const call of contentCalls) {
366
- expect(call).toContain(',')
367
- expect(call).toContain(' ')
368
- }
369
-
370
- destroyRenderer(renderer)
371
- })
372
-
373
- it('gradually reveals text over iterations', async () => {
374
- const renderer = createRenderer(DEFAULT_THEME)
375
- const contentCalls: string[] = []
376
- const box = {
377
- setContent: mock((content: string) => {
378
- contentCalls.push(content)
379
- }),
380
- } as any
381
- const screen = renderer.screen
382
- const currentLines: string[] = []
383
- const newLine = 'Test'
384
-
385
- await glitchLine(box, screen, currentLines, newLine, 3)
386
-
387
- // Should have multiple calls showing progression
388
- expect(contentCalls.length).toBeGreaterThan(1)
389
- // Last call should have the final text
390
- expect(contentCalls[contentCalls.length - 1]).toBe('Test')
391
-
392
- destroyRenderer(renderer)
393
- })
394
-
395
- it('handles empty lines', async () => {
396
- const renderer = createRenderer(DEFAULT_THEME)
397
- const box = {
398
- setContent: mock(() => {}),
399
- } as any
400
- const screen = renderer.screen
401
- const currentLines: string[] = []
402
- const newLine = ''
403
-
404
- await glitchLine(box, screen, currentLines, newLine, 2)
405
-
406
- expect(box.setContent).toHaveBeenCalled()
407
-
408
- destroyRenderer(renderer)
409
- })
410
- })
411
-
412
- describe('lineByLineReveal', () => {
413
- it('reveals content line by line', async () => {
414
- const renderer = createRenderer(DEFAULT_THEME)
415
- const box = {
416
- setContent: mock(() => {}),
417
- } as any
418
- const screen = renderer.screen
419
- const content = 'Line 1\nLine 2\nLine 3'
420
-
421
- await lineByLineReveal(box, screen, content, DEFAULT_THEME)
422
-
423
- // Should have called setContent multiple times
424
- expect(box.setContent).toHaveBeenCalled()
425
-
426
- destroyRenderer(renderer)
427
- })
428
-
429
- it('uses theme animations configuration', async () => {
430
- const customTheme = {
431
- ...DEFAULT_THEME,
432
- animations: {
433
- ...DEFAULT_THEME.animations,
434
- lineDelay: 10,
435
- glitchIterations: 2,
436
- },
437
- }
438
-
439
- const renderer = createRenderer(customTheme)
440
- const box = {
441
- setContent: mock(() => {}),
442
- } as any
443
- const screen = renderer.screen
444
- const content = 'Line A\nLine B'
445
-
446
- await lineByLineReveal(box, screen, content, customTheme)
447
-
448
- expect(box.setContent).toHaveBeenCalled()
449
-
450
- destroyRenderer(renderer)
451
- })
452
-
453
- it('handles single line content', async () => {
454
- const renderer = createRenderer(DEFAULT_THEME)
455
- const box = {
456
- setContent: mock(() => {}),
457
- } as any
458
- const screen = renderer.screen
459
- const content = 'Single line'
460
-
461
- await lineByLineReveal(box, screen, content, DEFAULT_THEME)
462
-
463
- expect(box.setContent).toHaveBeenCalled()
464
-
465
- destroyRenderer(renderer)
466
- })
467
-
468
- it('handles empty content', async () => {
469
- const renderer = createRenderer(DEFAULT_THEME)
470
- const box = {
471
- setContent: mock(() => {}),
472
- } as any
473
- const screen = renderer.screen
474
- const content = ''
475
-
476
- await lineByLineReveal(box, screen, content, DEFAULT_THEME)
477
-
478
- expect(box.setContent).toHaveBeenCalled()
479
-
480
- destroyRenderer(renderer)
481
- })
482
-
483
- it('skips delay for empty lines', async () => {
484
- const renderer = createRenderer(DEFAULT_THEME)
485
- const box = {
486
- setContent: mock(() => {}),
487
- } as any
488
- const screen = renderer.screen
489
- const content = 'Line 1\n\nLine 3'
490
-
491
- const startTime = Date.now()
492
- await lineByLineReveal(box, screen, content, DEFAULT_THEME)
493
- const duration = Date.now() - startTime
494
-
495
- // Should complete relatively quickly since empty line doesn't add delay
496
- expect(duration).toBeLessThan(1000)
497
- expect(box.setContent).toHaveBeenCalled()
498
-
499
- destroyRenderer(renderer)
500
- })
501
-
502
- it('processes multi-line content sequentially', async () => {
503
- const renderer = createRenderer(DEFAULT_THEME)
504
- const contentCalls: string[] = []
505
- const box = {
506
- setContent: mock((content: string) => {
507
- contentCalls.push(content)
508
- }),
509
- } as any
510
- const screen = renderer.screen
511
- const content = 'First\nSecond\nThird'
512
-
513
- await lineByLineReveal(box, screen, content, DEFAULT_THEME)
514
-
515
- // Should have accumulated lines progressively
516
- expect(contentCalls.length).toBeGreaterThan(0)
517
- // Final call should have all lines
518
- const finalCall = contentCalls[contentCalls.length - 1]
519
- expect(finalCall).toContain('First')
520
- expect(finalCall).toContain('Second')
521
- expect(finalCall).toContain('Third')
522
-
523
- destroyRenderer(renderer)
524
- })
525
- })