Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ku-progress-bar",
"version": "1.0.0-rc.2",
"version": "1.0.0-rc.3",
"description": "cli progress bar",
"main": "dist/index.js",
"homepage": "https://github.com/kos984/ku-cli-progress",
Expand Down Expand Up @@ -90,6 +90,12 @@
"functions": 100,
"lines": 100,
"statements": -10
},
"src/examples": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ sonar.projectKey=kos984_ku-cli-progress
sonar.organization=kos984
sonar.javascript.lcov.reportPaths=./coverage/lcov.info
sonar.eslint.reportPaths=./reports/eslint-report.json
sonar.coverage.exclusions=**/__tests__/*,**/examples/**
sonar.coverage.exclusions=**/__tests__/*,**/examples/**,**/__mocks__/*
# Scope of duplication detection
sonar.cpd.exclusions=**/__tests__/*
sonar.cpd.exclusions=**/__tests__/*,**/examples/**,**/__mocks__/*

# This is the name and version displayed in the SonarCloud UI.
#sonar.projectName=ku-cli-progress
Expand Down
64 changes: 35 additions & 29 deletions src/examples/composite-progress/composite-progress.example.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ jest.mock('../../lib/terminals/terminal-tty');
jest.mock('../helpers/loop-progresses');
jest.mock('../../lib/formatters/bars-formatter');
jest.mock('../../lib/time/time');
jest.mock('chalk', () => {
return {
green: (str: string) => `<green>${str}</green>`,
yellowBright: (str: string) => `<yellowBright>${str}</yellowBright>`,
};
});

import { TerminalTty } from '../../lib/terminals/terminal-tty';
import { bar } from './composite-progress.example';
Expand All @@ -25,16 +31,16 @@ describe('CompositeProgressComponent', () => {
exampleBarTestHelper.iterate(progress => progress.getTotal() / MAX_STEPS);
const calls = terminalMock.write.mock.calls.map(call => call[0]);
expect(calls).toMatchObject([
'[00001111░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 200/1000 ( 20% eta: ∞)\x1B[39m \x1B[93mwrite: 100/1000 ( 10% eta: ∞)\x1B[39m\n',
'[000000001111░░░░░░░░░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 300/1000 ( 30% eta: 4s)\x1B[39m \x1B[93mwrite: 200/1000 ( 20% eta: 4s)\x1B[39m\n',
'[0000000000001111░░░░░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 400/1000 ( 40% eta: 4s)\x1B[39m \x1B[93mwrite: 300/1000 ( 30% eta: 5s)\x1B[39m\n',
'[00000000000000001111░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 500/1000 ( 50% eta: 4s)\x1B[39m \x1B[93mwrite: 400/1000 ( 40% eta: 5s)\x1B[39m\n',
'[000000000000000000001111░░░░░░░░░░░░░░░░] \x1B[32mread: 600/1000 ( 60% eta: 3s)\x1B[39m \x1B[93mwrite: 500/1000 ( 50% eta: 4s)\x1B[39m\n',
'[0000000000000000000000001111░░░░░░░░░░░░] \x1B[32mread: 700/1000 ( 70% eta: 3s)\x1B[39m \x1B[93mwrite: 600/1000 ( 60% eta: 4s)\x1B[39m\n',
'[00000000000000000000000000001111░░░░░░░░] \x1B[32mread: 800/1000 ( 80% eta: 2s)\x1B[39m \x1B[93mwrite: 700/1000 ( 70% eta: 3s)\x1B[39m\n',
'[000000000000000000000000000000001111░░░░] \x1B[32mread: 900/1000 ( 90% eta: 1s)\x1B[39m \x1B[93mwrite: 800/1000 ( 80% eta: 2s)\x1B[39m\n',
'[0000000000000000000000000000000000001111] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 900/1000 ( 90% eta: 1s)\x1B[39m\n',
'[0000000000000000000000000000000000000000] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[00001111░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] <green>read: 200/1000 ( 20% eta: ∞)</green> <yellowBright>write: 100/1000 ( 10% eta: ∞)</yellowBright>\n',
'[000000001111░░░░░░░░░░░░░░░░░░░░░░░░░░░░] <green>read: 300/1000 ( 30% eta: 4s)</green> <yellowBright>write: 200/1000 ( 20% eta: 4s)</yellowBright>\n',
'[0000000000001111░░░░░░░░░░░░░░░░░░░░░░░░] <green>read: 400/1000 ( 40% eta: 4s)</green> <yellowBright>write: 300/1000 ( 30% eta: 5s)</yellowBright>\n',
'[00000000000000001111░░░░░░░░░░░░░░░░░░░░] <green>read: 500/1000 ( 50% eta: 4s)</green> <yellowBright>write: 400/1000 ( 40% eta: 5s)</yellowBright>\n',
'[000000000000000000001111░░░░░░░░░░░░░░░░] <green>read: 600/1000 ( 60% eta: 3s)</green> <yellowBright>write: 500/1000 ( 50% eta: 4s)</yellowBright>\n',
'[0000000000000000000000001111░░░░░░░░░░░░] <green>read: 700/1000 ( 70% eta: 3s)</green> <yellowBright>write: 600/1000 ( 60% eta: 4s)</yellowBright>\n',
'[00000000000000000000000000001111░░░░░░░░] <green>read: 800/1000 ( 80% eta: 2s)</green> <yellowBright>write: 700/1000 ( 70% eta: 3s)</yellowBright>\n',
'[000000000000000000000000000000001111░░░░] <green>read: 900/1000 ( 90% eta: 1s)</green> <yellowBright>write: 800/1000 ( 80% eta: 2s)</yellowBright>\n',
'[0000000000000000000000000000000000001111] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 900/1000 ( 90% eta: 1s)</yellowBright>\n',
'[0000000000000000000000000000000000000000] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
]);
});

Expand All @@ -44,15 +50,15 @@ describe('CompositeProgressComponent', () => {
);
const calls = terminalMock.write.mock.calls.map(call => call[0]);
expect(calls).toMatchObject([
'[00000000░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 200/1000 ( 20% eta: ∞)\x1B[39m \x1B[93mwrite: 200/1000 ( 20% eta: ∞)\x1B[39m\n',
'[0000000000001111░░░░░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 300/1000 ( 30% eta: 4s)\x1B[39m \x1B[93mwrite: 400/1000 ( 40% eta: 2s)\x1B[39m\n',
'[000000000000000011111111░░░░░░░░░░░░░░░░] \x1B[32mread: 400/1000 ( 40% eta: 4s)\x1B[39m \x1B[93mwrite: 600/1000 ( 60% eta: 1s)\x1B[39m\n',
'[00000000000000000000111111111111░░░░░░░░] \x1B[32mread: 500/1000 ( 50% eta: 4s)\x1B[39m \x1B[93mwrite: 800/1000 ( 80% eta: 1s)\x1B[39m\n',
'[0000000000000000000000001111111111111111] \x1B[32mread: 600/1000 ( 60% eta: 3s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[0000000000000000000000000000111111111111] \x1B[32mread: 700/1000 ( 70% eta: 3s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[0000000000000000000000000000000011111111] \x1B[32mread: 800/1000 ( 80% eta: 2s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[0000000000000000000000000000000000001111] \x1B[32mread: 900/1000 ( 90% eta: 1s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[0000000000000000000000000000000000000000] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[00000000░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] <green>read: 200/1000 ( 20% eta: ∞)</green> <yellowBright>write: 200/1000 ( 20% eta: ∞)</yellowBright>\n',
'[0000000000001111░░░░░░░░░░░░░░░░░░░░░░░░] <green>read: 300/1000 ( 30% eta: 4s)</green> <yellowBright>write: 400/1000 ( 40% eta: 2s)</yellowBright>\n',
'[000000000000000011111111░░░░░░░░░░░░░░░░] <green>read: 400/1000 ( 40% eta: 4s)</green> <yellowBright>write: 600/1000 ( 60% eta: 1s)</yellowBright>\n',
'[00000000000000000000111111111111░░░░░░░░] <green>read: 500/1000 ( 50% eta: 4s)</green> <yellowBright>write: 800/1000 ( 80% eta: 1s)</yellowBright>\n',
'[0000000000000000000000001111111111111111] <green>read: 600/1000 ( 60% eta: 3s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
'[0000000000000000000000000000111111111111] <green>read: 700/1000 ( 70% eta: 3s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
'[0000000000000000000000000000000011111111] <green>read: 800/1000 ( 80% eta: 2s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
'[0000000000000000000000000000000000001111] <green>read: 900/1000 ( 90% eta: 1s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
'[0000000000000000000000000000000000000000] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
]);
});

Expand All @@ -65,16 +71,16 @@ describe('CompositeProgressComponent', () => {
);
const calls = terminalMock.write.mock.calls.map(call => call[0]);
expect(calls).toMatchObject([
'[000011111111░░░░░░░░░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 300/1000 ( 30% eta: ∞)\x1B[39m \x1B[93mwrite: 100/1000 ( 10% eta: ∞)\x1B[39m\n',
'[00000000111111111111░░░░░░░░░░░░░░░░░░░░] \x1B[32mread: 500/1000 ( 50% eta: 1s)\x1B[39m \x1B[93mwrite: 200/1000 ( 20% eta: 4s)\x1B[39m\n',
'[0000000000001111111111111111░░░░░░░░░░░░] \x1B[32mread: 700/1000 ( 70% eta: 1s)\x1B[39m \x1B[93mwrite: 300/1000 ( 30% eta: 5s)\x1B[39m\n',
'[000000000000000011111111111111111111░░░░] \x1B[32mread: 900/1000 ( 90% eta: 0s)\x1B[39m \x1B[93mwrite: 400/1000 ( 40% eta: 5s)\x1B[39m\n',
'[0000000000000000000011111111111111111111] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 500/1000 ( 50% eta: 4s)\x1B[39m\n',
'[0000000000000000000000001111111111111111] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 600/1000 ( 60% eta: 4s)\x1B[39m\n',
'[0000000000000000000000000000111111111111] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 700/1000 ( 70% eta: 3s)\x1B[39m\n',
'[0000000000000000000000000000000011111111] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 800/1000 ( 80% eta: 2s)\x1B[39m\n',
'[0000000000000000000000000000000000001111] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 900/1000 ( 90% eta: 1s)\x1B[39m\n',
'[0000000000000000000000000000000000000000] \x1B[32mread: 1000/1000 ( 100% eta: 0s)\x1B[39m \x1B[93mwrite: 1000/1000 ( 100% eta: 0s)\x1B[39m\n',
'[000011111111░░░░░░░░░░░░░░░░░░░░░░░░░░░░] <green>read: 300/1000 ( 30% eta: ∞)</green> <yellowBright>write: 100/1000 ( 10% eta: ∞)</yellowBright>\n',
'[00000000111111111111░░░░░░░░░░░░░░░░░░░░] <green>read: 500/1000 ( 50% eta: 1s)</green> <yellowBright>write: 200/1000 ( 20% eta: 4s)</yellowBright>\n',
'[0000000000001111111111111111░░░░░░░░░░░░] <green>read: 700/1000 ( 70% eta: 1s)</green> <yellowBright>write: 300/1000 ( 30% eta: 5s)</yellowBright>\n',
'[000000000000000011111111111111111111░░░░] <green>read: 900/1000 ( 90% eta: 0s)</green> <yellowBright>write: 400/1000 ( 40% eta: 5s)</yellowBright>\n',
'[0000000000000000000011111111111111111111] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 500/1000 ( 50% eta: 4s)</yellowBright>\n',
'[0000000000000000000000001111111111111111] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 600/1000 ( 60% eta: 4s)</yellowBright>\n',
'[0000000000000000000000000000111111111111] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 700/1000 ( 70% eta: 3s)</yellowBright>\n',
'[0000000000000000000000000000000011111111] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 800/1000 ( 80% eta: 2s)</yellowBright>\n',
'[0000000000000000000000000000000000001111] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 900/1000 ( 90% eta: 1s)</yellowBright>\n',
'[0000000000000000000000000000000000000000] <green>read: 1000/1000 ( 100% eta: 0s)</green> <yellowBright>write: 1000/1000 ( 100% eta: 0s)</yellowBright>\n',
]);
});
});
105 changes: 105 additions & 0 deletions src/examples/helpers/__tests__/loop-progresses.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { loopProgresses, start } from '../loop-progresses';
import { IProgress } from '../../../lib/interfaces/progress.interface';

// Mock console.error to avoid noise in tests
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

describe('loop-progresses', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

describe('loopProgresses', () => {
it('should loop progresses with default delay', () => {
const mockProgress1 = {
increment: jest.fn(),
getProgress: jest.fn().mockReturnValue(0),
} as unknown as IProgress;

const mockProgress2 = {
increment: jest.fn(),
getProgress: jest.fn().mockReturnValue(0),
} as unknown as IProgress;

const progresses = [mockProgress1, mockProgress2];
const intervals = loopProgresses(progresses);

expect(intervals).toHaveLength(2);
expect(intervals[0]).toBeDefined();
expect(intervals[1]).toBeDefined();

// Fast-forward time to trigger intervals
jest.advanceTimersByTime(100);

expect(mockProgress1.increment).toHaveBeenCalled();
expect(mockProgress2.increment).toHaveBeenCalled();
});

it('should loop progresses with custom delay function', () => {
const mockProgress = {
increment: jest.fn(),
getProgress: jest.fn().mockReturnValue(0),
} as unknown as IProgress;

const customDelay = jest.fn().mockReturnValue(200);
const progresses = [mockProgress];
const intervals = loopProgresses(progresses, { getDelay: customDelay });

expect(intervals).toHaveLength(1);
expect(customDelay).toHaveBeenCalled();

// Fast-forward time to trigger interval
jest.advanceTimersByTime(200);

expect(mockProgress.increment).toHaveBeenCalled();
});

it('should stop interval when progress reaches 1', () => {
const mockProgress = {
increment: jest.fn(),
getProgress: jest.fn().mockReturnValue(1), // Always return 1 (complete)
} as unknown as IProgress;

const progresses = [mockProgress];
const intervals = loopProgresses(progresses);

expect(intervals).toHaveLength(1);

// Fast-forward time to trigger interval
jest.advanceTimersByTime(100);

// Should increment once, then check and stop
expect(mockProgress.increment).toHaveBeenCalledTimes(1);
});

it('should handle empty progresses array', () => {
const intervals = loopProgresses([]);
expect(intervals).toHaveLength(0);
});
});

describe('start', () => {
it('should execute function and catch errors', () => {
const mockFunction = jest.fn().mockRejectedValue(new Error('Test error'));

start(mockFunction);

// The function should be called immediately
expect(mockFunction).toHaveBeenCalled();
});

it('should execute function successfully', () => {
const mockFunction = jest.fn().mockResolvedValue(undefined);

start(mockFunction);

// The function should be called immediately
expect(mockFunction).toHaveBeenCalled();
});
});
});
131 changes: 131 additions & 0 deletions src/examples/helpers/__tests__/seed-random.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { SeededRandom } from '../seed-random';

describe('SeededRandom', () => {
describe('constructor', () => {
it('should initialize with positive seed', () => {
const rnd = new SeededRandom(12345);
expect(rnd.seed).toBe(12345);
});

it('should handle negative seed by adding 2147483646', () => {
const rnd = new SeededRandom(-5);
expect(rnd.seed).toBe(2147483641); // -5 + 2147483646
});

it('should handle zero seed by adding 2147483646', () => {
const rnd = new SeededRandom(0);
expect(rnd.seed).toBe(2147483646);
});

it('should handle large seed by modulo operation', () => {
const rnd = new SeededRandom(3000000000);
expect(rnd.seed).toBe(852516353); // 3000000000 % 2147483647
});

it('should handle large negative seed', () => {
const rnd = new SeededRandom(-3000000000);
expect(rnd.seed).toBe(1294967293); // (-3000000000 % 2147483647) + 2147483646
});
});

describe('next', () => {
it('should generate consistent values with same seed', () => {
const rnd1 = new SeededRandom(12345);
const rnd2 = new SeededRandom(12345);

const values1 = [rnd1.next(), rnd1.next(), rnd1.next()];
const values2 = [rnd2.next(), rnd2.next(), rnd2.next()];

expect(values1).toEqual(values2);
});

it('should generate different values with different seeds', () => {
const rnd1 = new SeededRandom(12345);
const rnd2 = new SeededRandom(54321);

const value1 = rnd1.next();
const value2 = rnd2.next();

expect(value1).not.toBe(value2);
});

it('should generate values between 0 and 1', () => {
const rnd = new SeededRandom(12345);

for (let i = 0; i < 100; i++) {
const value = rnd.next();
expect(value).toBeGreaterThanOrEqual(0);
expect(value).toBeLessThan(1);
}
});

it('should update internal seed', () => {
const rnd = new SeededRandom(12345);
const initialSeed = rnd.seed;

rnd.next();
const newSeed = rnd.seed;

expect(newSeed).not.toBe(initialSeed);
});
});

describe('nextInRange', () => {
it('should generate values within specified range', () => {
const rnd = new SeededRandom(12345);

for (let i = 0; i < 100; i++) {
const value = rnd.nextInRange(5, 10);
expect(value).toBeGreaterThanOrEqual(5);
expect(value).toBeLessThanOrEqual(10);
}
});

it('should handle single value range', () => {
const rnd = new SeededRandom(12345);

for (let i = 0; i < 10; i++) {
const value = rnd.nextInRange(7, 7);
expect(value).toBe(7);
}
});

it('should handle negative range', () => {
const rnd = new SeededRandom(12345);

for (let i = 0; i < 100; i++) {
const value = rnd.nextInRange(-10, -5);
expect(value).toBeGreaterThanOrEqual(-10);
expect(value).toBeLessThanOrEqual(-5);
}
});

it('should generate consistent values with same seed', () => {
const rnd1 = new SeededRandom(12345);
const rnd2 = new SeededRandom(12345);

const values1 = [
rnd1.nextInRange(1, 10),
rnd1.nextInRange(1, 10),
rnd1.nextInRange(1, 10),
];
const values2 = [
rnd2.nextInRange(1, 10),
rnd2.nextInRange(1, 10),
rnd2.nextInRange(1, 10),
];

expect(values1).toEqual(values2);
});

it('should handle large range', () => {
const rnd = new SeededRandom(12345);

for (let i = 0; i < 100; i++) {
const value = rnd.nextInRange(1, 1000);
expect(value).toBeGreaterThanOrEqual(1);
expect(value).toBeLessThanOrEqual(1000);
}
});
});
});
6 changes: 3 additions & 3 deletions src/examples/helpers/seed-random.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
export class SeededRandom {
protected seed: number;
public seed: number;

public constructor(seed) {
this.seed = seed % 2147483647;
if (this.seed <= 0) this.seed += 2147483646;
}

public next() {
public next(): number {
this.seed = (this.seed * 16807) % 2147483647;
return (this.seed - 1) / 2147483646;
}

nextInRange(min, max) {
nextInRange(min, max): number {
return Math.floor(this.next() * (max - min + 1)) + min;
}
}
Loading
Loading