diff --git a/README.md b/README.md index e78147a..ecf5e34 100755 --- a/README.md +++ b/README.md @@ -250,9 +250,9 @@ The following options can be changed - `stopOnComplete` (type:boolean) - automatically call `stop()` when the value reaches the total (default: false) - `clearOnComplete` (type:boolean) - clear the progress bar on complete / `stop()` call (default: false) - `barsize` (type:int) - the length of the progress bar in chars (default: 40) -- `align` (type:char) - position of the progress bar - 'left' (default), 'right' or 'center' -- `barCompleteChar` (type:char) - character to use as "complete" indicator in the bar (default: "=") -- `barIncompleteChar` (type:char) - character to use as "incomplete" indicator in the bar (default: "-") +- `align` (type:string) - position of the progress bar - 'left' (default), 'right' or 'center' +- `barCompleteChar` (type:string|string[]) - character to use as "complete" indicator in the bar (default: "=") +- `barIncompleteChar` (type:string) - character to use as "incomplete" indicator in the bar (default: "-") - `hideCursor` (type:boolean) - hide the cursor during progress operation; restored on complete (default: false) - pass `null` to keep terminal settings - `linewrap` (type:boolean) - disable line wrapping (default: false) - pass `null` to keep terminal settings; pass `true` to add linebreaks automatically (not recommended) - `gracefulExit` (type:boolean) - stop the bars in case of `SIGINT` or `SIGTERM` - this restores most cursor settings before exiting (default: `false` - subjected to change) @@ -450,7 +450,7 @@ The following presets are included by default * **shades-classic** - Unicode background shades are used for the bar * **shades-grey** - Unicode background shades with grey bar * **rect** - Unicode Rectangles - +* **braille-patterns** - Unicode Braille Pattern Dots Compatibility --------------------------------------------- diff --git a/cli-progress.js b/cli-progress.js index 54ab313..0e8d5e9 100755 --- a/cli-progress.js +++ b/cli-progress.js @@ -1,21 +1,19 @@ -const _SingleBar = require('./lib/single-bar'); -const _MultiBar = require('./lib/multi-bar'); -const _Presets = require('./presets/index'); -const _Formatter = require('./lib/formatter'); -const _defaultFormatValue = require('./lib/format-value'); -const _defaultFormatBar = require('./lib/format-bar'); -const _defaultFormatTime = require('./lib/format-time'); +const SingleBar = require('./lib/single-bar.js'); +const MultiBar = require('./lib/multi-bar.js'); +const Presets = require('./presets/index.js'); +const Formatter = require('./lib/formatter.js'); +const ValueFormat = require('./lib/format-value.js'); +const BarFormat = require('./lib/format-bar.js'); +const TimeFormat = require('./lib/format-time.js'); // sub-module access module.exports = { - Bar: _SingleBar, - SingleBar: _SingleBar, - MultiBar: _MultiBar, - Presets: _Presets, + Bar: SingleBar, + SingleBar, + MultiBar, + Presets, Format: { - Formatter: _Formatter, - BarFormat: _defaultFormatBar, - ValueFormat: _defaultFormatValue, - TimeFormat: _defaultFormatTime + Formatter, + BarFormat, ValueFormat, TimeFormat, } -}; \ No newline at end of file +}; diff --git a/lib/format-bar.js b/lib/format-bar.js index ec7b82f..19d82cd 100644 --- a/lib/format-bar.js +++ b/lib/format-bar.js @@ -1,11 +1,23 @@ // format bar module.exports = function formatBar(progress, options){ - // calculate barsize - const completeSize = Math.round(progress*options.barsize); - const incompleteSize = options.barsize-completeSize; - - // generate bar string by stripping the pre-rendered strings - return options.barCompleteString.substr(0, completeSize) + + if (Array.isArray(options.barCompleteChar) && options.barCompleteChar.length > 1) { + const completeSize = Math.floor(progress * options.barsize); + const incompleteSize = Math.floor(options.barsize * (1 - progress)); + const remainder = progress * options.barsize - completeSize; + const remainderChar = remainder > 0 ? + options.barCompleteChar[Math.round(remainder * options.barCompleteChar.length) - 1] : + ""; + return options.barCompleteString.slice(0, completeSize) + + remainderChar + options.barGlue + - options.barIncompleteString.substr(0, incompleteSize); -} \ No newline at end of file + options.barIncompleteString.slice(0, incompleteSize); + } + + // calculate barsize + const completeSize = Math.round(progress * options.barsize); + const incompleteSize = options.barsize - completeSize; + // generate bar string by stripping the pre-rendered strings + return options.barCompleteString.slice(0, completeSize) + + options.barGlue + + options.barIncompleteString.slice(0, incompleteSize); +} diff --git a/lib/generic-bar.js b/lib/generic-bar.js index 2e5d11e..2f28722 100755 --- a/lib/generic-bar.js +++ b/lib/generic-bar.js @@ -1,20 +1,20 @@ -const _ETA = require('./eta'); -const _Terminal = require('./terminal'); -const _formatter = require('./formatter'); -const _options = require('./options'); -const _EventEmitter = require('events'); +const EventEmitter = require('node:events'); + +const ETA = require('./eta.js'); +const Terminal = require('./terminal.js'); +const formatter = require('./formatter.js'); // Progress-Bar constructor -module.exports = class GenericBar extends _EventEmitter{ +module.exports = class GenericBar extends EventEmitter { - constructor(options){ + constructor(options) { super(); // store options and assign derived ones (instance specific) - this.options = _options.assignDerivedOptions(options); + this.options = options; // store terminal instance - this.terminal = (this.options.terminal) ? this.options.terminal : new _Terminal(this.options.stream); + this.terminal = (this.options.terminal) ? this.options.terminal : new Terminal(this.options.stream); // the current bar value this.value = 0; @@ -38,7 +38,7 @@ module.exports = class GenericBar extends _EventEmitter{ this.lastRedraw = Date.now(); // default eta calculator (will be re-create on start) - this.eta = new _ETA(this.options.etaBufferLength, 0, 0); + this.eta = new ETA(this.options.etaBufferLength, 0, 0); // payload data this.payload = {}; @@ -47,7 +47,7 @@ module.exports = class GenericBar extends _EventEmitter{ this.isActive = false; // use default formatter or custom one ? - this.formatter = (typeof this.options.format === 'function') ? this.options.format : _formatter; + this.formatter = (typeof this.options.format === 'function') ? this.options.format : formatter; } // internal render function @@ -123,7 +123,7 @@ module.exports = class GenericBar extends _EventEmitter{ this.lastDrawnString = ''; // initialize eta buffer - this.eta = new _ETA(this.options.etaBufferLength, this.startTime, this.value); + this.eta = new ETA(this.options.etaBufferLength, this.startTime, this.value); // set flag this.isActive = true; @@ -136,7 +136,7 @@ module.exports = class GenericBar extends _EventEmitter{ stop(){ // set flag this.isActive = false; - + // store stop timestamp to get total duration this.stopTime = Date.now(); @@ -207,7 +207,7 @@ module.exports = class GenericBar extends _EventEmitter{ // handle the use case when `step` is omitted but payload is passed if (typeof arg0 === 'object') { this.update(this.value + 1, arg0); - + // increment([step=1], [payload={}]) }else{ this.update(this.value + arg0, arg1); diff --git a/lib/multi-bar.js b/lib/multi-bar.js index d40ccfc..7a1bebb 100644 --- a/lib/multi-bar.js +++ b/lib/multi-bar.js @@ -1,25 +1,26 @@ -const _Terminal = require('./terminal'); -const _BarElement = require('./generic-bar'); -const _options = require('./options'); -const _EventEmitter = require('events'); +const EventEmitter = require('node:events'); + +const Terminal = require('./terminal.js'); +const BarElement = require('./generic-bar.js'); +const { parse, } = require('./options.js'); // Progress-Bar constructor -module.exports = class MultiBar extends _EventEmitter{ +module.exports = class MultiBar extends EventEmitter { - constructor(options, preset){ + constructor(options, preset) { super(); // list of bars this.bars = []; // parse+store options - this.options = _options.parse(options, preset); + this.options = parse(options, preset); // disable synchronous updates this.options.synchronousUpdate = false; // store terminal instance - this.terminal = (this.options.terminal) ? this.options.terminal : new _Terminal(this.options.stream); + this.terminal = (this.options.terminal) ? this.options.terminal : new Terminal(this.options.stream); // the update timer this.timer = null; @@ -38,14 +39,14 @@ module.exports = class MultiBar extends _EventEmitter{ } // add a new bar to the stack - create(total, startValue, payload, barOptions={}){ + create(total, startValue, payload, barOptions={}) { // create new bar element and merge global options + overrides // use the same global terminal instance for all instances - const bar = new _BarElement(Object.assign( - {}, + const bar = new BarElement(Object.assign( + {}, // global options - this.options, + this.options, // terminal instance { @@ -70,19 +71,19 @@ module.exports = class MultiBar extends _EventEmitter{ process.once('SIGINT', this.sigintCallback); process.once('SIGTERM', this.sigintCallback); } - + // multiprogress already active ? - if (!this.isActive){ + if (!this.isActive) { // hide the cursor ? - if (this.options.hideCursor === true){ + if (this.options.hideCursor === true) { this.terminal.cursor(false); } // disable line wrapping ? - if (this.options.linewrap === false){ + if (this.options.linewrap === false) { this.terminal.lineWrapping(false); } - + // initialize update timer this.timer = setTimeout(this.update.bind(this), this.schedulingRate); } @@ -101,12 +102,12 @@ module.exports = class MultiBar extends _EventEmitter{ } // remove a bar from the stack - remove(bar){ + remove(bar) { // find element const index = this.bars.indexOf(bar); // element found ? - if (index < 0){ + if (index < 0) { return false; } @@ -133,7 +134,7 @@ module.exports = class MultiBar extends _EventEmitter{ // trigger event this.emit('update-pre'); - + // reset cursor this.terminal.cursorRelativeReset(); @@ -218,7 +219,7 @@ module.exports = class MultiBar extends _EventEmitter{ if (this.options.clearOnComplete){ // clear all bars this.terminal.clearBottom(); - + // or show final progress ? }else{ // update each bar diff --git a/lib/options.js b/lib/options.js index 4671d4a..5a4105d 100644 --- a/lib/options.js +++ b/lib/options.js @@ -1,110 +1,117 @@ // utility to merge defaults -function mergeOption(v, defaultValue){ - if (typeof v === 'undefined' || v === null){ - return defaultValue; - }else{ - return v; - } +function mergeOption(v, defaultValue) { + return typeof v === 'undefined' || v === null ? defaultValue : v; } -module.exports = { - // set global options - parse: function parse(rawOptions, preset){ +// set global options +function parse(rawOptions, preset) { - // options storage - const options = {}; + // options storage + const options = {}; - // merge preset - const opt = Object.assign({}, preset, rawOptions); + // merge preset + const opt = Object.assign({}, preset, rawOptions); - // the max update rate in fps (redraw will only triggered on value change) - options.throttleTime = 1000 / (mergeOption(opt.fps, 10)); + // the maximum update rate in fps (redraw will only be triggered on value change) + options.throttleTime = 1000 / (mergeOption(opt.fps, 10)); - // the output stream to write on - options.stream = mergeOption(opt.stream, process.stderr); + // the output stream to write on + options.stream = mergeOption(opt.stream, process.stderr); - // external terminal provided ? - options.terminal = mergeOption(opt.terminal, null); + // external terminal provided ? + options.terminal = mergeOption(opt.terminal, null); - // clear on finish ? - options.clearOnComplete = mergeOption(opt.clearOnComplete, false); + // clear on completion? + options.clearOnComplete = mergeOption(opt.clearOnComplete, false); - // stop on finish ? - options.stopOnComplete = mergeOption(opt.stopOnComplete, false); + // stop on completion? + options.stopOnComplete = mergeOption(opt.stopOnComplete, false); - // size of the progressbar in chars - options.barsize = mergeOption(opt.barsize, 40); + // size of the progressbar in chars + options.barsize = mergeOption(opt.barsize, 40); - // position of the progress bar - 'left' (default), 'right' or 'center' - options.align = mergeOption(opt.align, 'left'); + // position of the progress bar - 'left' (default), 'right' or 'center' + options.align = mergeOption(opt.align, 'left'); - // hide the cursor ? - options.hideCursor = mergeOption(opt.hideCursor, false); + // hide the cursor? + options.hideCursor = mergeOption(opt.hideCursor, false); - // disable linewrapping ? - options.linewrap = mergeOption(opt.linewrap, false); + // disable line wrapping? + options.linewrap = mergeOption(opt.linewrap, false); - // glue sequence (control chars) between bar elements ? - options.barGlue = mergeOption(opt.barGlue, ''); + // glue sequence (control chars) between bar elements ? + options.barGlue = mergeOption(opt.barGlue, ''); - // bar chars - options.barCompleteChar = mergeOption(opt.barCompleteChar, '='); - options.barIncompleteChar = mergeOption(opt.barIncompleteChar, '-'); + // bar chars + options.barCompleteChar = mergeOption(opt.barCompleteChar, '='); + options.barIncompleteChar = mergeOption(opt.barIncompleteChar, '-'); - // the bar format - options.format = mergeOption(opt.format, 'progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}'); + // the bar format + options.format = mergeOption(opt.format, 'progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}'); - // external time-format provided ? - options.formatTime = mergeOption(opt.formatTime, null); + // external time-format provided? + options.formatTime = mergeOption(opt.formatTime, null); - // external value-format provided ? - options.formatValue = mergeOption(opt.formatValue, null); + // external value-format provided? + options.formatValue = mergeOption(opt.formatValue, null); - // external bar-format provided ? - options.formatBar = mergeOption(opt.formatBar, null); + // external bar-format provided? + options.formatBar = mergeOption(opt.formatBar, null); - // the number of results to average ETA over - options.etaBufferLength = mergeOption(opt.etaBuffer, 10); + // the number of results to average ETA over + options.etaBufferLength = mergeOption(opt.etaBuffer, 10); - // automatic eta updates based on fps - options.etaAsynchronousUpdate = mergeOption(opt.etaAsynchronousUpdate, false); + // automatic eta updates based on fps + options.etaAsynchronousUpdate = mergeOption(opt.etaAsynchronousUpdate, false); - // progress calculation relative to start value ? default start at 0 - options.progressCalculationRelative = mergeOption(opt.progressCalculationRelative, false); + // progress calculation relative to start value ? default start at 0 + options.progressCalculationRelative = mergeOption(opt.progressCalculationRelative, false); - // allow synchronous updates ? - options.synchronousUpdate = mergeOption(opt.synchronousUpdate, true); + // allow synchronous updates ? + options.synchronousUpdate = mergeOption(opt.synchronousUpdate, true); - // notty mode - options.noTTYOutput = mergeOption(opt.noTTYOutput, false); + // notty mode + options.noTTYOutput = mergeOption(opt.noTTYOutput, false); - // schedule - 2s - options.notTTYSchedule = mergeOption(opt.notTTYSchedule, 2000); - - // emptyOnZero - false - options.emptyOnZero = mergeOption(opt.emptyOnZero, false); + // schedule - 2s + options.notTTYSchedule = mergeOption(opt.notTTYSchedule, 2000); - // force bar redraw even if progress did not change - options.forceRedraw = mergeOption(opt.forceRedraw, false); + // emptyOnZero - false + options.emptyOnZero = mergeOption(opt.emptyOnZero, false); - // automated padding to fixed width ? - options.autopadding = mergeOption(opt.autopadding, false); + // force bar redraw even if progress did not change + options.forceRedraw = mergeOption(opt.forceRedraw, false); - // stop bar on SIGINT/SIGTERM to restore cursor settings ? - options.gracefulExit = mergeOption(opt.gracefulExit, false); + // automated padding to fixed width ? + options.autopadding = mergeOption(opt.autopadding, false); - return options; - }, + // stop bar on SIGINT/SIGTERM to restore cursor settings ? + options.gracefulExit = mergeOption(opt.gracefulExit, false); - // derived options: instance specific, has to be created for every bar element - assignDerivedOptions: function assignDerivedOptions(options){ - // pre-render bar strings (performance) - options.barCompleteString = options.barCompleteChar.repeat(options.barsize + 1); - options.barIncompleteString = options.barIncompleteChar.repeat(options.barsize + 1); + // Computed property to pre-render bar strings for performance + Object.defineProperty(options, "barCompleteString", { + get () { + return Array.isArray(this.barCompleteChar) ? + this.barCompleteChar[this.barCompleteChar.length - 1].repeat(this.barsize + 1) : + this.barCompleteChar.repeat(this.barsize + 1); + }, + }); - // autopadding character - empty in case autopadding is disabled - options.autopaddingChar = options.autopadding ? mergeOption(options.autopaddingChar, ' ') : ''; + // Computed property to pre-render bar strings for performance + Object.defineProperty(options, "barIncompleteString", { + get () { + return this.barIncompleteChar.repeat(this.barsize + 1); + }, + }); - return options; - } -}; \ No newline at end of file + // Computed property to set the auto-padding character - empty in case auto-padding is disabled + Object.defineProperty(options, "autopaddingChar", { + get () { + return this.autopadding ? mergeOption(this.autopaddingChar, ' ') : ''; + }, + }); + + return options; +} + +module.exports = { parse, }; diff --git a/lib/single-bar.js b/lib/single-bar.js index e8235e4..9a91ca2 100644 --- a/lib/single-bar.js +++ b/lib/single-bar.js @@ -1,11 +1,11 @@ -const _GenericBar = require('./generic-bar'); -const _options = require('./options'); +const GenericBar = require('./generic-bar.js'); +const { parse, } = require('./options.js'); // Progress-Bar constructor -module.exports = class SingleBar extends _GenericBar{ +module.exports = class SingleBar extends GenericBar { - constructor(options, preset){ - super(_options.parse(options, preset)); + constructor(options, preset) { + super(parse(options, preset)); // the update timer this.timer = null; @@ -23,7 +23,7 @@ module.exports = class SingleBar extends _GenericBar{ } // internal render function - render(){ + render() { // stop timer if (this.timer){ clearTimeout(this.timer); @@ -42,7 +42,7 @@ module.exports = class SingleBar extends _GenericBar{ this.timer = setTimeout(this.render.bind(this), this.schedulingRate); } - update(current, payload){ + update(current, payload) { // timer inactive ? if (!this.timer) { return; @@ -51,7 +51,7 @@ module.exports = class SingleBar extends _GenericBar{ super.update(current, payload); // trigger synchronous update ? - // check for throttle time + // check for throttle time if (this.options.synchronousUpdate && (this.lastRedraw + this.options.throttleTime*2) < Date.now()){ // force update this.render(); @@ -59,7 +59,7 @@ module.exports = class SingleBar extends _GenericBar{ } // start the progress bar - start(total, startValue, payload){ + start(total, startValue, payload) { // progress updates are only visible in TTY mode! if (this.options.noTTYOutput === false && this.terminal.isTTY() === false){ return; @@ -93,14 +93,14 @@ module.exports = class SingleBar extends _GenericBar{ } // stop the bar - stop(){ + stop() { // timer inactive ? if (!this.timer) { return; } - + // remove sigint listener - if (this.sigintCallback){ + if (this.sigintCallback) { process.removeListener('SIGINT', this.sigintCallback); process.removeListener('SIGTERM', this.sigintCallback); this.sigintCallback = null; @@ -117,12 +117,12 @@ module.exports = class SingleBar extends _GenericBar{ this.timer = null; // cursor hidden ? - if (this.options.hideCursor === true){ + if (this.options.hideCursor === true) { this.terminal.cursor(true); } // re-enable line wrapping ? - if (this.options.linewrap === false){ + if (this.options.linewrap === false) { this.terminal.lineWrapping(true); } @@ -130,12 +130,12 @@ module.exports = class SingleBar extends _GenericBar{ this.terminal.cursorRestore(); // clear line on complete ? - if (this.options.clearOnComplete){ + if (this.options.clearOnComplete) { this.terminal.cursorTo(0, null); this.terminal.clearLine(); - }else{ + } else { // new line on complete this.terminal.newline(); } } -} \ No newline at end of file +} diff --git a/presets/braille-patterns.js b/presets/braille-patterns.js new file mode 100644 index 0000000..a0fc249 --- /dev/null +++ b/presets/braille-patterns.js @@ -0,0 +1,9 @@ +// Unicode Braille Pattern Dot style +// as used in Docker CLI +// ⣀⣄⣤⣦⣶⣷⣿ + +module.exports = { + format: ' {bar} {percentage}% | ETA: {eta}s | {value}/{total}', + barCompleteChar: ['\u28C4', '\u28E4', '\u28E6', '\u28F6', '\u28F7', '\u28FF'], + barIncompleteChar: '\u28C0', +}; diff --git a/presets/index.js b/presets/index.js index 471831d..fca54e5 100644 --- a/presets/index.js +++ b/presets/index.js @@ -1,11 +1,13 @@ -const _legacy = require('./legacy'); -const _shades_classic = require('./shades-classic'); -const _shades_grey = require('./shades-grey'); -const _rect = require('./rect'); +const legacy = require('./legacy.js'); +const shades_classic = require('./shades-classic.js'); +const shades_grey = require('./shades-grey.js'); +const rect = require('./rect.js'); +const braille_patterns = require('./braille-patterns.js'); module.exports = { - legacy: _legacy, - shades_classic: _shades_classic, - shades_grey: _shades_grey, - rect: _rect -}; \ No newline at end of file + legacy, + shades_classic, + shades_grey, + rect, + braille_patterns, +}; diff --git a/test/lib/braille-patterns.test.js b/test/lib/braille-patterns.test.js new file mode 100644 index 0000000..6da03dc --- /dev/null +++ b/test/lib/braille-patterns.test.js @@ -0,0 +1,82 @@ +const assert = require('node:assert'); +const formatter = require('../../lib/formatter.js'); +const { parse } = require('../../lib/options.js'); +const braille_patterns_preset = require("../../presets/braille-patterns.js"); + +const defaultOptions = parse({}, braille_patterns_preset); +const defaultParams = { + progress: 0.32, + eta: '20', + startTime: 1571383670022, + total: 100, + value: 20, + maxWidth: 147 +}; +const defaultPayload = {}; + +describe('braille pattern preset', () => { + it ('should properly render default values', () => { + const result = formatter(defaultOptions, defaultParams, defaultPayload); + const expected = ' ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 32% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should properly render custom `barCompleteChar` configuration with default values', () => { + const options = parse( + Object.assign( + {}, + defaultOptions, + { + // ⡀⣀⣄⣤⣦⣶⣷⣿ + barCompleteChar: ['\u2840', '\u28C0', '\u28C4', '\u28E4', '\u28E6', '\u28F6', '\u28F7', '\u28FF'], + barIncompleteChar: ' ', + } + ) + ); + const result = formatter(options, defaultParams, defaultPayload); + const expected = ' ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶ 32% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should render one dot at 1/240', () => { + const params = Object.assign({}, defaultParams, { progress: 1 / 240 }); + const result = formatter(defaultOptions, params, defaultPayload); + const expected = ' ⣄⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 0% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should render two dots at 2/240', () => { + const params = Object.assign({}, defaultParams, { progress: 2 / 240 }); + const result = formatter(defaultOptions, params, defaultPayload); + const expected = ' ⣤⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 0% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should render three dots at 3/240', () => { + const params = Object.assign({}, defaultParams, { progress: 3 / 240 }); + const result = formatter(defaultOptions, params, defaultPayload); + const expected = ' ⣦⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 1% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should render four dots at 4/240', () => { + const params = Object.assign({}, defaultParams, { progress: 4 / 240 }); + const result = formatter(defaultOptions, params, defaultPayload); + const expected = ' ⣶⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 1% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should render five dots at 5/240', () => { + const params = Object.assign({}, defaultParams, { progress: 5 / 240 }); + const result = formatter(defaultOptions, params, defaultPayload); + const expected = ' ⣷⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 2% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); + + it ('should render six dots at 6/240', () => { + const params = Object.assign({}, defaultParams, { progress: 6 / 240 }); + const result = formatter(defaultOptions, params, defaultPayload); + const expected = ' ⣿⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ 2% | ETA: 20s | 20/100'; + assert.equal(result, expected); + }); +}); diff --git a/test/lib/formatter.test.js b/test/lib/formatter.test.js index d8cc491..4e4b76f 100644 --- a/test/lib/formatter.test.js +++ b/test/lib/formatter.test.js @@ -1,120 +1,115 @@ -const _assert = require('assert'); -const _formatter = require('../../lib/formatter'); -const _defaults = { - options: { - throttleTime: 100, - stream: null, - terminal: null, - clearOnComplete: false, - stopOnComplete: false, - barsize: 40, - align: 'left', - hideCursor: false, - linewrap: false, - barCompleteString: '████████████████████████████████████████', - barIncompleteString: '░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░', - format: ' {bar} {percentage}% | ETA: {eta}s | {value}/{total}', - etaBufferLength: 10, - synchronousUpdate: true, - noTTYOutput: false, - notTTYSchedule: 2000, - emptyOnZero: false, - forceRedraw: false, - autopadding: false, - autopaddingChar: '', - formatBar: null, - formatTime: null, - formatValue: null, - barGlue: '' - }, - params: { - progress: 0.2, - eta: '20', - startTime: 1571383670022, - total: 100, - value: 20, - maxWidth: 147 - }, - payload: { - - } +const assert = require('node:assert'); +const formatter = require('../../lib/formatter.js'); + +const defaultOptions = { + throttleTime: 100, + stream: null, + terminal: null, + clearOnComplete: false, + stopOnComplete: false, + barsize: 40, + align: 'left', + hideCursor: false, + linewrap: false, + barCompleteString: '████████████████████████████████████████', + barIncompleteString: '░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░', + format: ' {bar} {percentage}% | ETA: {eta}s | {value}/{total}', + etaBufferLength: 10, + synchronousUpdate: true, + noTTYOutput: false, + notTTYSchedule: 2000, + emptyOnZero: false, + forceRedraw: false, + autopadding: false, + autopaddingChar: '', + formatBar: null, + formatTime: null, + formatValue: null, + barGlue: '' }; +const defaultParams = { + progress: 0.2, + eta: '20', + startTime: 1571383670022, + total: 100, + value: 20, + maxWidth: 147 +}; +const defaultPayload = {}; -describe('formatter', function() { - let defaults = null; - - beforeEach('set defaults', () => { - defaults = _defaults; - }); - - it ('should proper render default values', () => { - const {options, params, payload} = defaults; - - const result = _formatter(options, params, payload); +describe('formatter', () => { + it ('should properly render default values', () => { + const result = formatter(defaultOptions, defaultParams, defaultPayload); const expected = ' ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20% | ETA: 20s | 20/100'; - _assert.equal(result, expected); + assert.equal(result, expected); }); it('should leave placeholder for undefined values', () => { - const {options, params, payload} = defaults; - - options.format = '{undefined_value} {bar} {percentage}%'; + const options = Object.assign( + {}, defaultOptions, + { format: '{undefined_value} {bar} {percentage}%', } + ); + const payload = Object.assign({}, defaultPayload); delete payload.undefined_value; - const result = _formatter(options, params, payload); + const result = formatter(options, defaultParams, payload); const expected = '{undefined_value} ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20%'; - _assert.equal(result, expected); + assert.equal(result, expected); }); describe('render falsy values', () => { it('should render zero value', () => { - const {options, params, payload} = defaults; - - options.format = '{zero_value} {bar} {percentage}%'; - payload.zero_value = 0; + const options = Object.assign( + {}, defaultOptions, + { format: '{zero_value} {bar} {percentage}%', } + ); + const payload = Object.assign({}, defaultPayload, { zero_value: 0 }); - const result = _formatter(options, params, payload); + const result = formatter(options, defaultParams, payload); const expected = '0 ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20%'; - _assert.equal(result, expected); + assert.equal(result, expected); }); it('should render empty string value', () => { - const {options, params, payload} = defaults; - - options.format = '{empty_string} {bar} {percentage}%'; - payload.empty_string = ''; + const options = Object.assign( + {}, defaultOptions, + { format: '{empty_string} {bar} {percentage}%', } + ); + const payload = Object.assign({}, defaultPayload, { empty_string: '' }); - const result = _formatter(options, params, payload); + const result = formatter(options, defaultParams, payload); const expected = ' ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20%'; - _assert.equal(result, expected); + assert.equal(result, expected); }); it('should render null value', () => { - const {options, params, payload} = defaults; + const options = Object.assign( + {}, defaultOptions, + { format: '{null_value} {bar} {percentage}%', } + ); + const payload = Object.assign({}, defaultPayload, { null_value: null }); - options.format = '{null_value} {bar} {percentage}%'; - payload.null_value = null; - - const result = _formatter(options, params, payload); + const result = formatter(options, defaultParams, payload); const expected = 'null ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20%'; - _assert.equal(result, expected); + assert.equal(result, expected); }); it('should render boolean false value', () => { - const {options, params, payload} = defaults; - - options.format = '{false_value} {bar} {percentage}%'; - payload.false_value = false; + const options = Object.assign( + {}, defaultOptions, + { format: '{false_value} {bar} {percentage}%', } + ); + const payload = Object.assign({}, defaultPayload, { false_value: false }); - const result = _formatter(options, params, payload); + const result = formatter(options, defaultParams, payload); const expected = 'false ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 20%'; - _assert.equal(result, expected); + assert.equal(result, expected); }); }); -}); \ No newline at end of file +});