Skip to content

Commit d88147d

Browse files
committed
test(utils): test log levels and log groups
1 parent 3d158ff commit d88147d

File tree

1 file changed

+240
-0
lines changed

1 file changed

+240
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import ansis from 'ansis';
2+
import type { MockInstance } from 'vitest';
3+
import { Logger } from './logger.js';
4+
5+
describe('Logger', () => {
6+
let stdout = '';
7+
let consoleLogSpy: MockInstance<unknown[], void>;
8+
let performanceNowSpy: MockInstance<[], number>;
9+
let mathRandomSpy: MockInstance<[], number>;
10+
11+
beforeAll(() => {
12+
vi.useFakeTimers();
13+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(message => {
14+
stdout += `${message}\n`;
15+
});
16+
});
17+
18+
beforeEach(() => {
19+
stdout = '';
20+
performanceNowSpy = vi.spyOn(performance, 'now');
21+
mathRandomSpy = vi.spyOn(Math, 'random');
22+
23+
vi.stubEnv('CI', 'false');
24+
vi.stubEnv('GITHUB_ACTIONS', 'false');
25+
vi.stubEnv('GITLAB_CI', 'false');
26+
});
27+
28+
afterAll(() => {
29+
vi.useRealTimers();
30+
consoleLogSpy.mockReset();
31+
performanceNowSpy.mockReset();
32+
mathRandomSpy.mockReset();
33+
});
34+
35+
it('should colorize logs based on level', () => {
36+
vi.stubEnv('CP_VERBOSE', 'true'); // to render debug log
37+
const logger = new Logger();
38+
39+
logger.info('Code PushUp CLI');
40+
logger.debug('v1.2.3');
41+
logger.warn('Config file in CommonJS format');
42+
logger.error('Failed to load config');
43+
44+
expect(stdout).toBe(
45+
`
46+
Code PushUp CLI
47+
${ansis.gray('v1.2.3')}
48+
${ansis.yellow('Config file in CommonJS format')}
49+
${ansis.red('Failed to load config')}
50+
`.trimStart(),
51+
);
52+
});
53+
54+
it('should omit debug logs if not verbose', () => {
55+
vi.stubEnv('CP_VERBOSE', 'false');
56+
57+
new Logger().debug('Found config file code-pushup.config.js');
58+
59+
expect(stdout).toBe('');
60+
});
61+
62+
it('should set verbose flag and environment variable', () => {
63+
vi.stubEnv('CP_VERBOSE', 'false');
64+
const logger = new Logger();
65+
66+
logger.setVerbose(true);
67+
68+
expect(logger.isVerbose()).toBe(true);
69+
expect(process.env['CP_VERBOSE']).toBe('true');
70+
expect(new Logger().isVerbose()).toBe(true);
71+
});
72+
73+
it('should group logs with symbols and print duration', async () => {
74+
performanceNowSpy.mockReturnValueOnce(0).mockReturnValueOnce(1234); // group duration: 1.23 s
75+
const logger = new Logger();
76+
77+
await logger.group('Running plugin "ESLint"', async () => {
78+
logger.info('$ npx eslint . --format=json');
79+
logger.warn('Skipping unknown rule "deprecation/deprecation"');
80+
return 'ESLint reported 4 errors and 11 warnings';
81+
});
82+
83+
expect(ansis.strip(stdout)).toBe(`
84+
❯ Running plugin "ESLint"
85+
│ $ npx eslint . --format=json
86+
│ Skipping unknown rule "deprecation/deprecation"
87+
└ ESLint reported 4 errors and 11 warnings (1.23 s)
88+
89+
`);
90+
});
91+
92+
it('should complete group logs when error is thrown', async () => {
93+
const logger = new Logger();
94+
95+
await expect(
96+
logger.group('Running plugin "ESLint"', async () => {
97+
logger.info(
98+
'$ npx eslint . --format=json --output-file=.code-pushup/eslint/results.json',
99+
);
100+
throw new Error(
101+
"ENOENT: no such file or directory, open '.code-pushup/eslint/results.json'",
102+
);
103+
}),
104+
).rejects.toThrow(
105+
"ENOENT: no such file or directory, open '.code-pushup/eslint/results.json'",
106+
);
107+
expect(stdout).toBe(
108+
`
109+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
110+
${ansis.cyan('│')} $ npx eslint . --format=json --output-file=.code-pushup/eslint/results.json
111+
${ansis.cyan('└')} ${ansis.red("Error: ENOENT: no such file or directory, open '.code-pushup/eslint/results.json'")}
112+
113+
`,
114+
);
115+
});
116+
117+
it('should alternate colors for log groups and preserve child log styles', async () => {
118+
performanceNowSpy
119+
.mockReturnValueOnce(0)
120+
.mockReturnValueOnce(1234) // 1st group duration: 1.23 s
121+
.mockReturnValueOnce(0)
122+
.mockReturnValueOnce(12_000) // 2nd group duration: 12 s
123+
.mockReturnValueOnce(0)
124+
.mockReturnValueOnce(42); // 3rd group duration: 42 ms
125+
const logger = new Logger();
126+
127+
await logger.group('Running plugin "ESLint"', async () => {
128+
logger.info(`${ansis.blue('$')} npx eslint . --format=json`);
129+
logger.warn('Skipping unknown rule "deprecation/deprecation"');
130+
return 'ESLint reported 4 errors and 11 warnings';
131+
});
132+
133+
await logger.group(
134+
'Running plugin "Lighthouse"',
135+
async () => 'Calculated Lighthouse scores for 4 categories',
136+
);
137+
138+
await logger.group('Running plugin "Code coverage"', async () => {
139+
logger.info(`${ansis.blue('$')} npx vitest --coverage.enabled`);
140+
return `Total line coverage is ${ansis.bold('82%')}`;
141+
});
142+
143+
expect(stdout).toBe(
144+
`
145+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
146+
${ansis.cyan('│')} ${ansis.blue('$')} npx eslint . --format=json
147+
${ansis.cyan('│')} ${ansis.yellow('Skipping unknown rule "deprecation/deprecation"')}
148+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(1.23 s)')}
149+
150+
${ansis.bold.magenta('❯ Running plugin "Lighthouse"')}
151+
${ansis.magenta('└')} ${ansis.green('Calculated Lighthouse scores for 4 categories')} ${ansis.gray('(12 s)')}
152+
153+
${ansis.bold.cyan('❯ Running plugin "Code coverage"')}
154+
${ansis.cyan('│')} ${ansis.blue('$')} npx vitest --coverage.enabled
155+
${ansis.cyan('└')} ${ansis.green(`Total line coverage is ${ansis.bold('82%')}`)} ${ansis.gray('(42 ms)')}
156+
157+
`,
158+
);
159+
});
160+
161+
it('should use log group prefix in child loggers', async () => {
162+
performanceNowSpy.mockReturnValueOnce(0).mockReturnValueOnce(1234); // group duration: 1.23 s
163+
164+
await new Logger().group('Running plugin "ESLint"', async () => {
165+
new Logger().info(`${ansis.blue('$')} npx eslint . --format=json`);
166+
return 'ESLint reported 4 errors and 11 warnings';
167+
});
168+
169+
expect(stdout).toBe(`
170+
${ansis.bold.cyan('❯ Running plugin "ESLint"')}
171+
${ansis.cyan('│')} ${ansis.blue('$')} npx eslint . --format=json
172+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(1.23 s)')}
173+
174+
`);
175+
});
176+
177+
it('should use workflow commands to group logs in GitHub Actions environment', async () => {
178+
vi.stubEnv('CI', 'true');
179+
vi.stubEnv('GITHUB_ACTIONS', 'true');
180+
performanceNowSpy.mockReturnValueOnce(0).mockReturnValueOnce(1234); // group duration: 1.23 s
181+
const logger = new Logger();
182+
183+
await logger.group('Running plugin "ESLint"', async () => {
184+
logger.info('$ npx eslint . --format=json');
185+
logger.warn('Skipping unknown rule "deprecation/deprecation"');
186+
return 'ESLint reported 4 errors and 11 warnings';
187+
});
188+
189+
expect(ansis.strip(stdout)).toBe(`
190+
::group::Running plugin "ESLint"
191+
│ $ npx eslint . --format=json
192+
│ Skipping unknown rule "deprecation/deprecation"
193+
└ ESLint reported 4 errors and 11 warnings (1.23 s)
194+
::endgroup::
195+
196+
`);
197+
});
198+
199+
it('should use collapsible sections in GitLab CI/CD environment, initial collapse depends on verbosity', async () => {
200+
vi.stubEnv('CI', 'true');
201+
vi.stubEnv('GITLAB_CI', 'true');
202+
vi.setSystemTime(new Date(123456789000)); // current Unix timestamp: 123456789 seconds since epoch
203+
performanceNowSpy
204+
.mockReturnValueOnce(0)
205+
.mockReturnValueOnce(123) // 1st group duration: 123 ms
206+
.mockReturnValueOnce(0)
207+
.mockReturnValue(45); // 2nd group duration: 45 ms
208+
mathRandomSpy
209+
.mockReturnValueOnce(0x1a / Math.pow(2, 8)) // 1st group's random ID: "1a"
210+
.mockReturnValueOnce(0x1b / Math.pow(2, 8)); // 2nd group's random ID: "1b"
211+
const logger = new Logger();
212+
213+
logger.setVerbose(false);
214+
await logger.group('Running plugin "ESLint"', async () => {
215+
logger.info(`${ansis.blue('$')} npx eslint . --format=json`);
216+
logger.warn('Skipping unknown rule "deprecation/deprecation"');
217+
return 'ESLint reported 4 errors and 11 warnings';
218+
});
219+
logger.setVerbose(true);
220+
await logger.group('Running plugin "Code coverage"', async () => {
221+
logger.info(`${ansis.blue('$')} npx vitest --coverage.enabled`);
222+
return `Total line coverage is ${ansis.bold('82%')}`;
223+
});
224+
225+
// debugging tip: temporarily remove '\r' character from original implementation
226+
expect(stdout).toBe(`
227+
\x1b[0Ksection_start:123456789:code_pushup_logs_group_1a[collapsed=true]\r\x1b[0K${ansis.bold.cyan('❯ Running plugin "ESLint"')}
228+
${ansis.cyan('│')} ${ansis.blue('$')} npx eslint . --format=json
229+
${ansis.cyan('│')} ${ansis.yellow('Skipping unknown rule "deprecation/deprecation"')}
230+
${ansis.cyan('└')} ${ansis.green('ESLint reported 4 errors and 11 warnings')} ${ansis.gray('(123 ms)')}
231+
\x1b[0Ksection_end:123456789:code_pushup_logs_group_1a\r\x1b[0K
232+
233+
\x1b[0Ksection_start:123456789:code_pushup_logs_group_1b\r\x1b[0K${ansis.bold.magenta('❯ Running plugin "Code coverage"')}
234+
${ansis.magenta('│')} ${ansis.blue('$')} npx vitest --coverage.enabled
235+
${ansis.magenta('└')} ${ansis.green(`Total line coverage is ${ansis.bold('82%')}`)} ${ansis.gray('(45 ms)')}
236+
\x1b[0Ksection_end:123456789:code_pushup_logs_group_1b\r\x1b[0K
237+
238+
`);
239+
});
240+
});

0 commit comments

Comments
 (0)