Skip to content

Commit 50cd17d

Browse files
committed
test(utils): test spinners combined with groups
1 parent 2ca91c0 commit 50cd17d

File tree

1 file changed

+317
-2
lines changed

1 file changed

+317
-2
lines changed

packages/utils/src/lib/logger.int.test.ts

Lines changed: 317 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('Logger', () => {
4949
moveCursor: () => true,
5050
clearLine: () => {
5151
const idx = output.lastIndexOf('\n');
52-
output = idx >= 0 ? output.substring(0, idx) : '';
52+
output = idx >= 0 ? output.substring(0, idx + 1) : '';
5353
return true;
5454
},
5555
};
@@ -291,7 +291,7 @@ ${ansis.magenta('└')} ${ansis.green(`Total line coverage is ${ansis.bold('82%'
291291
performanceNowSpy.mockReturnValueOnce(0).mockReturnValueOnce(42); // task duration: 42 ms
292292
});
293293

294-
it('should render spinner for async tasks', async () => {
294+
it('should render dots spinner for async tasks', async () => {
295295
const task = new Logger().task(
296296
'Uploading report to portal',
297297
async () => 'Uploaded report to portal',
@@ -397,6 +397,33 @@ ${logSymbols.success} Uploaded report to portal ${ansis.gray('(42 ms)')}
397397
);
398398
});
399399

400+
it('should print other logs once spinner fails', async () => {
401+
vi.stubEnv('CP_VERBOSE', 'true');
402+
const logger = new Logger();
403+
404+
const task = logger.task('Uploading report to portal', async () => {
405+
logger.debug('Sent request to Portal API');
406+
await new Promise(resolve => {
407+
setTimeout(resolve, 42);
408+
});
409+
logger.debug('Received response from Portal API');
410+
throw new Error('GraphQL error: Invalid API key');
411+
});
412+
413+
expect(output).toBe(`${ansis.cyan('⠋')} Uploading report to portal`);
414+
415+
vi.advanceTimersByTime(42);
416+
await expect(task).rejects.toThrow('GraphQL error: Invalid API key');
417+
418+
expect(output).toBe(
419+
`
420+
${logSymbols.error} Uploading report to portal → ${ansis.red('Error: GraphQL error: Invalid API key')}
421+
${ansis.gray('Sent request to Portal API')}
422+
${ansis.gray('Received response from Portal API')}
423+
`.trimStart(),
424+
);
425+
});
426+
400427
it('should print other logs immediately in CI', async () => {
401428
vi.stubEnv('CI', 'true');
402429
vi.stubEnv('CP_VERBOSE', 'true');
@@ -466,4 +493,292 @@ ${logSymbols.success} Uploaded report to portal ${ansis.gray('(42 ms)')}
466493
);
467494
});
468495
});
496+
497+
describe('spinners + groups', () => {
498+
beforeEach(() => {
499+
performanceNowSpy
500+
.mockReturnValueOnce(0)
501+
.mockReturnValueOnce(0)
502+
.mockReturnValueOnce(42) // task duration: 42 ms
503+
.mockReturnValueOnce(50); // group duration: 50 ms;
504+
});
505+
506+
it('should render line spinner for async tasks within group', async () => {
507+
const logger = new Logger();
508+
509+
const group = logger.group('Running plugin "ESLint"', async () => {
510+
await logger.command('npx eslint . --format=json', async () => {});
511+
logger.warn('Skipping unknown rule "deprecation/deprecation"');
512+
return 'ESLint reported 4 errors and 11 warnings';
513+
});
514+
515+
expect(output).toBe(
516+
`
517+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
518+
${ansis.cyan('-')} ${ansis.blue('$')} npx eslint . --format=json`,
519+
);
520+
521+
vi.advanceTimersByTime(cliSpinners.line.interval);
522+
expect(output).toBe(
523+
`
524+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
525+
${ansis.cyan('\\')} ${ansis.blue('$')} npx eslint . --format=json`,
526+
);
527+
528+
vi.advanceTimersByTime(cliSpinners.line.interval);
529+
expect(output).toBe(
530+
`
531+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
532+
${ansis.cyan('|')} ${ansis.blue('$')} npx eslint . --format=json`,
533+
);
534+
535+
vi.advanceTimersByTime(cliSpinners.line.interval);
536+
expect(output).toBe(
537+
`
538+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
539+
${ansis.cyan('/')} ${ansis.blue('$')} npx eslint . --format=json`,
540+
);
541+
542+
await expect(group).resolves.toBeUndefined();
543+
544+
expect(output).toBe(
545+
`
546+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
547+
${ansis.cyan('│')} ${ansis.green('$')} npx eslint . --format=json ${ansis.gray('(42 ms)')}
548+
${ansis.cyan('│')} ${ansis.yellow('Skipping unknown rule "deprecation/deprecation"')}
549+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(50 ms)')}
550+
551+
`,
552+
);
553+
});
554+
555+
it('should colorize line spinner with same color as group', async () => {
556+
const logger = new Logger();
557+
558+
const group1 = logger.group('Running plugin "ESLint"', async () => {
559+
await logger.command('npx eslint . --format=json', async () => {});
560+
return 'ESLint reported 4 errors and 11 warnings';
561+
});
562+
563+
expect(output).toBe(
564+
`
565+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
566+
${ansis.cyan('-')} ${ansis.blue('$')} npx eslint . --format=json`,
567+
);
568+
569+
await group1;
570+
571+
performanceNowSpy
572+
.mockReturnValueOnce(0)
573+
.mockReturnValueOnce(0)
574+
.mockReturnValueOnce(cliSpinners.line.interval) // task duration
575+
.mockReturnValueOnce(cliSpinners.line.interval); // group duration
576+
577+
const group2 = logger.group('Running plugin "Lighthouse"', async () => {
578+
await logger.task(
579+
`Executing ${ansis.bold('runLighthouse')} function`,
580+
async () => {
581+
await new Promise(resolve => {
582+
setTimeout(resolve, cliSpinners.line.interval);
583+
});
584+
return `Executed ${ansis.bold('runLighthouse')} function`;
585+
},
586+
);
587+
return 'Calculated Lighthouse scores for 4 categories';
588+
});
589+
590+
expect(output).toBe(
591+
`
592+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
593+
${ansis.cyan('│')} ${ansis.green('$')} npx eslint . --format=json ${ansis.gray('(42 ms)')}
594+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(50 ms)')}
595+
596+
${ansis.bold.magenta('❯ Running plugin "Lighthouse"')}
597+
${ansis.magenta('-')} Executing ${ansis.bold('runLighthouse')} function`,
598+
);
599+
600+
vi.advanceTimersByTime(cliSpinners.line.interval);
601+
expect(output).toBe(
602+
`
603+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
604+
${ansis.cyan('│')} ${ansis.green('$')} npx eslint . --format=json ${ansis.gray('(42 ms)')}
605+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(50 ms)')}
606+
607+
${ansis.bold.magenta('❯ Running plugin "Lighthouse"')}
608+
${ansis.magenta('\\')} Executing ${ansis.bold('runLighthouse')} function`,
609+
);
610+
611+
await group2;
612+
expect(output).toBe(
613+
`
614+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
615+
${ansis.cyan('│')} ${ansis.green('$')} npx eslint . --format=json ${ansis.gray('(42 ms)')}
616+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(50 ms)')}
617+
618+
${ansis.bold.magenta('❯ Running plugin "Lighthouse"')}
619+
${ansis.magenta('│')} Executed ${ansis.bold('runLighthouse')} function ${ansis.gray('(130 ms)')}
620+
${ansis.magenta('└')} ${ansis.green('Calculated Lighthouse scores for 4 categories')} ${ansis.gray('(130 ms)')}
621+
622+
`,
623+
);
624+
});
625+
626+
it('should skip interactive group spinner in CI', async () => {
627+
vi.stubEnv('CI', 'true');
628+
const logger = new Logger();
629+
630+
const group = logger.group('Running plugin "ESLint"', async () => {
631+
await logger.command('npx eslint . --format=json', async () => {});
632+
return 'ESLint reported 4 errors and 11 warnings';
633+
});
634+
635+
expect(output).toBe(
636+
`
637+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
638+
${ansis.cyan('│')} ${ansis.blue('$')} npx eslint . --format=json
639+
`,
640+
);
641+
642+
await group;
643+
644+
expect(output).toBe(
645+
`
646+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
647+
${ansis.cyan('│')} ${ansis.blue('$')} npx eslint . --format=json
648+
${ansis.cyan('│')} ${ansis.green('$')} npx eslint . --format=json ${ansis.gray('(42 ms)')}
649+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(50 ms)')}
650+
651+
`,
652+
);
653+
});
654+
655+
it('should fail group if spinner task rejects', async () => {
656+
const logger = new Logger();
657+
658+
const group = logger.group('Running plugin "ESLint"', async () => {
659+
await logger.command('npx eslint . --format=json', async () => {
660+
await new Promise(resolve => {
661+
setTimeout(resolve, 0);
662+
});
663+
throw new Error('Process failed with exit code 1');
664+
});
665+
return 'ESLint reported 4 errors and 11 warnings';
666+
});
667+
668+
expect(output).toBe(
669+
`
670+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
671+
${ansis.cyan('-')} ${ansis.blue('$')} npx eslint . --format=json`,
672+
);
673+
674+
vi.advanceTimersToNextTimer();
675+
await expect(group).rejects.toThrow('Process failed with exit code 1');
676+
677+
expect(output).toBe(
678+
`
679+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
680+
${ansis.cyan('│')} ${ansis.red('$')} npx eslint . --format=json
681+
${ansis.cyan('└')} ${ansis.red('Error: Process failed with exit code 1')}
682+
683+
`,
684+
);
685+
});
686+
687+
it('should fail spinner, complete group and exit if SIGINT received', async () => {
688+
vi.spyOn(process, 'exit').mockReturnValue(undefined as never);
689+
vi.spyOn(os, 'platform').mockReturnValue('win32');
690+
const logger = new Logger();
691+
692+
logger.group('Running plugin "ESLint"', async () => {
693+
await logger.command('npx eslint . --format=json', async () => {});
694+
return 'ESLint reported 4 errors and 11 warnings';
695+
});
696+
697+
expect(output).toBe(
698+
`
699+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
700+
${ansis.cyan('-')} ${ansis.blue('$')} npx eslint . --format=json`,
701+
);
702+
703+
process.emit('SIGINT');
704+
705+
expect(output).toBe(
706+
`
707+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
708+
${ansis.cyan('└')} ${ansis.blue('$')} npx eslint . --format=json ${ansis.red.bold('[SIGINT]')}
709+
710+
${ansis.red.bold('Cancelled by SIGINT')}
711+
`,
712+
);
713+
714+
expect(process.exit).toHaveBeenCalledWith(2);
715+
});
716+
717+
it('should indent other logs within group if they were logged while spinner was active', async () => {
718+
vi.stubEnv('CP_VERBOSE', 'true');
719+
const logger = new Logger();
720+
721+
const group = logger.group('Running plugin "ESLint"', async () => {
722+
await logger.command('npx eslint . --format=json', async () => {
723+
logger.debug('ESLint v9.0.0\n\nAll files pass linting.\n');
724+
});
725+
return 'ESLint reported 0 problems';
726+
});
727+
728+
expect(ansis.strip(output)).toBe(
729+
`
730+
❯ Running plugin "ESLint"
731+
- $ npx eslint . --format=json`,
732+
);
733+
734+
await expect(group).resolves.toBeUndefined();
735+
736+
expect(ansis.strip(output)).toBe(
737+
`
738+
❯ Running plugin "ESLint"
739+
│ $ npx eslint . --format=json (42 ms)
740+
│ ESLint v9.0.0
741+
742+
│ All files pass linting.
743+
744+
└ ESLint reported 0 problems (50 ms)
745+
746+
`,
747+
);
748+
});
749+
750+
it('should indent other logs from spinner in group when it fails in CI', async () => {
751+
vi.stubEnv('CI', 'true');
752+
const logger = new Logger();
753+
754+
const group = logger.group('Running plugin "ESLint"', async () => {
755+
await logger.command('npx eslint . --format=json', async () => {
756+
logger.error(
757+
"\nOops! Something went wrong! :(\n\nESLint: 8.26.0\n\nESLint couldn't find a configuration file.\n",
758+
);
759+
throw new Error('Process failed with exit code 2');
760+
});
761+
return 'ESLint reported 0 problems';
762+
});
763+
764+
await expect(group).rejects.toThrow('Process failed with exit code 2');
765+
766+
expect(ansis.strip(output)).toBe(
767+
`
768+
❯ Running plugin "ESLint"
769+
│ $ npx eslint . --format=json
770+
771+
│ Oops! Something went wrong! :(
772+
773+
│ ESLint: 8.26.0
774+
775+
│ ESLint couldn't find a configuration file.
776+
777+
│ $ npx eslint . --format=json
778+
└ Error: Process failed with exit code 2
779+
780+
`,
781+
);
782+
});
783+
});
469784
});

0 commit comments

Comments
 (0)