@supertape/formatter-progress-bar
Version:
📼 Supertape formatter progress bar
220 lines (171 loc) • 4.67 kB
JavaScript
import {Writable} from 'node:stream';
import cliProgress from 'cli-progress';
import chalk from 'chalk';
import fullstore from 'fullstore';
import {isCI} from 'ci-info';
import process from 'node:process';
global._isCI = isCI;
const OK = '👌';
const WARNING = '⚠️';
const YELLOW = '#f9d472';
const {red} = chalk;
const formatErrorsCount = (a) => a ? red(a) : OK;
const isStr = (a) => typeof a === 'string';
const {stderr} = process;
let SUPERTAPE_PROGRESS_BAR;
let SUPERTAPE_PROGRESS_BAR_MIN = 100;
let SUPERTAPE_PROGRESS_BAR_COLOR;
let SUPERTAPE_PROGRESS_BAR_STACK = 1;
export function createFormatter(bar) {
({
SUPERTAPE_PROGRESS_BAR,
SUPERTAPE_PROGRESS_BAR_MIN = 100,
SUPERTAPE_PROGRESS_BAR_COLOR,
SUPERTAPE_PROGRESS_BAR_STACK = 1,
} = process.env);
const out = createOutput();
const store = fullstore();
const barStore = fullstore(bar);
return {
start: start({
barStore,
out,
}),
test: test({
store,
}),
testEnd: testEnd({
barStore,
}),
fail: fail({
out,
store,
}),
comment: comment({
out,
}),
end: end({
barStore,
out,
}),
};
}
export const start = ({barStore, out}) => ({total}) => {
out('TAP version 13');
const color = SUPERTAPE_PROGRESS_BAR_COLOR || YELLOW;
const bar = barStore() || _createProgress({
total,
color,
test: '',
});
barStore(bar);
};
export const test = ({store}) => ({test}) => {
store(`# ${test}`);
};
export const testEnd = ({barStore}) => ({count, total, failed, test}) => {
barStore().increment({
count,
total,
test,
failed: formatErrorsCount(failed),
});
};
export const fail = ({out, store}) => ({at, count, message, operator, result, expected, output, errorStack}) => {
out('');
out(store());
out(`❌ not ok ${count} ${message}`);
out(' ---');
out(` operator: ${operator}`);
if (output)
out(output);
if (!isStr(output)) {
out(' expected: |-');
out(` ${expected}`);
out(' result: |-');
out(` ${result}`);
}
out(` ${at}`);
if (SUPERTAPE_PROGRESS_BAR_STACK !== '0') {
out(' stack: |-');
out(errorStack);
}
out(' ...');
out('');
};
export const comment = ({out}) => ({message}) => {
out(`# ${message}`);
};
export const end = ({barStore, out}) => ({count, passed, failed, skiped}) => {
barStore().stop();
out('');
out(`1..${count}`);
out(`# tests ${count}`);
out(`# pass ${passed}`);
if (skiped)
out(formatSkip(skiped));
out('');
if (failed)
out(`# ❌ fail ${failed}`);
if (!failed)
out(formatOk());
out('');
out('');
return `\r${out()}`;
};
function createOutput() {
let output = [];
return (...args) => {
const [line] = args;
if (!args.length) {
const result = output.join('\n');
output = [];
return result;
}
output.push(line);
};
}
const getColorFn = (color) => {
if (color.startsWith('#'))
return chalk.hex(color);
return chalk[color];
};
const defaultStreamOptions = {
total: Infinity,
};
const getStream = ({total} = defaultStreamOptions) => {
const is = total >= SUPERTAPE_PROGRESS_BAR_MIN;
if (is && !global._isCI || SUPERTAPE_PROGRESS_BAR === '1')
return stderr;
return new Writable();
};
export const _getStream = getStream;
function _createProgress({total, color, test}) {
const colorFn = getColorFn(color);
const bar = new cliProgress.SingleBar({
format: `${colorFn('{bar}')} {percentage}% | {failed} | {count}/{total} | {test}`,
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
clearOnComplete: true,
stopOnComplete: true,
hideCursor: true,
stream: getStream({
total,
}),
}, cliProgress.Presets.react);
bar.start(total, 0, {
test,
total,
count: 0,
failed: OK,
});
return bar;
}
function formatOk() {
const {TERMINAL_EMULATOR} = process.env;
const spaces = /JetBrains/.test(TERMINAL_EMULATOR) ? ' ' : '';
return `# ✅ ${spaces}ok`;
}
function formatSkip(skiped) {
return `# ${WARNING} skip ${skiped}`;
}