1- import { stripVTControlCharacters } from 'node:util' ;
21import { testSuite , expect } from 'manten' ;
32import { createFixture } from 'fs-fixture' ;
3+ import ansiEscapes from 'ansi-escapes' ;
4+ import yoctocolors from 'yoctocolors' ;
45import { node } from '../utils/node.js' ;
56import { tempDir } from '../utils/temp-dir.js' ;
67
78export default testSuite ( ( { describe } ) => {
89 describe ( 'ANSI control codes' , ( { test } ) => {
9- test ( 'uses ANSI clear and cursor movement codes' , async ( ) => {
10+ test ( 'uses ANSI clear and cursor movement codes during spinner animation ' , async ( ) => {
1011 await using fixture = await createFixture ( {
1112 'test.mjs' : `
1213 import task from '#tasuku';
@@ -19,37 +20,27 @@ export default testSuite(({ describe }) => {
1920 } , { tempDir } ) ;
2021
2122 const result = await node ( fixture . getPath ( 'test.mjs' ) ) ;
22-
23- // Task completes successfully
24- expect ( stripVTControlCharacters ( result . output ) . includes ( 'Task' ) ) . toBe ( true ) ;
25-
26- // ANSI codes for line clearing and cursor movement
27- // \x1B[2K = clear line
28- // \x1B[1A = move cursor up one line
29- expect ( result . output ) . toContain ( '\u001B[2K' ) ;
30- expect ( result . output ) . toContain ( '\u001B[1A' ) ;
31- } ) ;
32-
33- test ( 'ANSI clear codes present during spinner animation' , async ( ) => {
34- await using fixture = await createFixture ( {
35- 'test.mjs' : `
36- import task from '#tasuku';
37- import { setTimeout } from 'node:timers/promises';
38-
39- await task('Task', async () => {
40- await setTimeout(200);
41- });
42- ` ,
43- } , { tempDir } ) ;
44-
45- const result = await node ( fixture . getPath ( 'test.mjs' ) ) ;
46-
47- // Verify task completed
48- expect ( stripVTControlCharacters ( result . output ) . includes ( 'Task' ) ) . toBe ( true ) ;
49-
50- // ANSI codes for cursor movement and line clearing during spinner updates
51- expect ( result . output ) . toContain ( '\u001B[2K' ) ; // clear line
52- expect ( result . output ) . toContain ( '\u001B[1A' ) ; // move up
23+ expect ( result . stderr ) . toBe ( '' ) ;
24+
25+ // Hide cursor at start
26+ expect ( result . stdout ) . toContain ( ansiEscapes . cursorHide ) ;
27+
28+ // Multiple spinner frames showing animation
29+ const spinnerChars = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' . split ( '' ) ;
30+ const foundSpinners = spinnerChars . filter (
31+ char => result . stdout . includes ( yoctocolors . yellow ( char ) ) ,
32+ ) ;
33+ expect ( foundSpinners . length ) . toBeGreaterThan ( 1 ) ; // Multiple frames
34+
35+ // ANSI codes appear multiple times (once per frame update)
36+ const eraseLineCount = result . stdout . split ( ansiEscapes . eraseLine ) . length - 1 ;
37+ const cursorUpCount = result . stdout . split ( ansiEscapes . cursorUp ( ) ) . length - 1 ;
38+ expect ( eraseLineCount ) . toBeGreaterThan ( 1 ) ; // Multiple clears during animation
39+ expect ( cursorUpCount ) . toBeGreaterThan ( 1 ) ; // Multiple cursor moves
40+
41+ // Final state: green checkmark
42+ expect ( result . stdout ) . toContain ( yoctocolors . green ( '✔' ) ) ;
43+ expect ( result . stdout ) . toContain ( 'Task' ) ;
5344 } ) ;
5445
5546 test ( 'multiple line clears for multiple tasks' , async ( ) => {
@@ -67,16 +58,19 @@ export default testSuite(({ describe }) => {
6758 } , { tempDir } ) ;
6859
6960 const result = await node ( fixture . getPath ( 'test.mjs' ) ) ;
70- const textOutput = stripVTControlCharacters ( result . output ) ;
71-
72- // All task names appear
73- expect ( textOutput . includes ( 'one' ) ) . toBe ( true ) ;
74- expect ( textOutput . includes ( 'two' ) ) . toBe ( true ) ;
75- expect ( textOutput . includes ( 'three' ) ) . toBe ( true ) ;
76-
77- // Multiple ANSI clear codes for updating multiple tasks
78- expect ( result . output ) . toContain ( '\u001B[2K' ) ;
79- expect ( result . output ) . toContain ( '\u001B[1A' ) ;
61+ expect ( result . stderr ) . toBe ( '' ) ;
62+
63+ // All task names appear with checkmarks
64+ expect ( result . stdout ) . toContain ( 'one' ) ;
65+ expect ( result . stdout ) . toContain ( 'two' ) ;
66+ expect ( result . stdout ) . toContain ( 'three' ) ;
67+ expect ( result . stdout ) . toContain ( yoctocolors . green ( '✔' ) ) ;
68+
69+ // Count ANSI codes - should appear multiple times for multiple task updates
70+ const eraseLineCount = result . stdout . split ( ansiEscapes . eraseLine ) . length - 1 ;
71+ const cursorUpCount = result . stdout . split ( ansiEscapes . cursorUp ( ) ) . length - 1 ;
72+ expect ( eraseLineCount ) . toBeGreaterThan ( 1 ) ; // Multiple line clears
73+ expect ( cursorUpCount ) . toBeGreaterThan ( 1 ) ; // Multiple cursor moves
8074 } ) ;
8175
8276 test ( 'clearing task triggers ANSI clear codes' , async ( ) => {
@@ -94,18 +88,24 @@ export default testSuite(({ describe }) => {
9488 });
9589
9690 task1.clear();
97-
98- // Give renderer time to update
99- await setTimeout(50);
10091 ` ,
10192 } , { tempDir } ) ;
10293
10394 const result = await node ( fixture . getPath ( 'test.mjs' ) ) ;
104-
105- // Should contain ANSI clear codes (line clear and cursor up)
106- // These codes are used to clear Task 1 from the screen
107- expect ( result . output ) . toContain ( '\u001B[2K' ) ; // clear line
108- expect ( result . output ) . toContain ( '\u001B[1A' ) ; // move cursor up
95+ expect ( result . stderr ) . toBe ( '' ) ;
96+
97+ // Both tasks appear with checkmarks initially
98+ expect ( result . stdout ) . toContain ( 'Task 1' ) ;
99+ expect ( result . stdout ) . toContain ( 'Task 2' ) ;
100+ expect ( result . stdout ) . toContain ( yoctocolors . green ( '✔' ) ) ;
101+
102+ // Clearing triggers additional ANSI codes for re-rendering
103+ const eraseLineCount = result . stdout . split ( ansiEscapes . eraseLine ) . length - 1 ;
104+ const cursorUpCount = result . stdout . split ( ansiEscapes . cursorUp ( ) ) . length - 1 ;
105+ // Multiple clears for task updates + clear operation
106+ expect ( eraseLineCount ) . toBeGreaterThan ( 1 ) ;
107+ // Multiple cursor moves
108+ expect ( cursorUpCount ) . toBeGreaterThan ( 1 ) ;
109109 } ) ;
110110 } ) ;
111111} ) ;
0 commit comments