diff --git a/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts b/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts index bf0d843e74..07a9dd25b3 100644 --- a/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts +++ b/packages/contentstack-utilities/src/progress-summary/cli-progress-manager.ts @@ -421,14 +421,19 @@ export default class CLIProgressManager { if (!this.showConsoleLogs && process.progressBar) { const totalProcessed = process.current; - const percentage = Math.round((totalProcessed / process.total) * 100); + // Reconcile the bar's total with the number of items actually processed so the + // rendered "value/total" matches the "Complete (success/processed)" summary. A + // process registered with an estimated total larger than the items actually ticked + // would otherwise render mismatched counts, e.g. "37/37 | Complete (27/27)". + process.progressBar.setTotal(totalProcessed); + const percentage = totalProcessed > 0 ? 100 : 0; const formattedPercentage = this.formatPercentage(percentage); const statusText = success - ? getChalk().green(`✓ Complete (${process.successCount}/${process.current})`) - : getChalk().red(`✗ Failed (${process.successCount}/${process.current})`); + ? getChalk().green(`✓ Complete (${process.successCount}/${totalProcessed})`) + : getChalk().red(`✗ Failed (${process.successCount}/${totalProcessed})`); const displayName = this.formatProcessName(processName); const indentedLabel = ` ├─ ${displayName}`.padEnd(25); - process.progressBar.update(process.total, { + process.progressBar.update(totalProcessed, { label: success ? getChalk().green(indentedLabel) : getChalk().red(indentedLabel), status: statusText, percentage: formattedPercentage, diff --git a/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts b/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts index cb0752b48d..ab6f20ab35 100644 --- a/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts +++ b/packages/contentstack-utilities/test/unit/cliProgressManager.test.ts @@ -34,6 +34,7 @@ const mockProgressBar = { stop: sinon.stub(), increment: sinon.stub(), update: sinon.stub(), + setTotal: sinon.stub(), }; const mockMultiBar = { @@ -335,6 +336,74 @@ describe('CLIProgressManager', () => { }); }); + describe('Complete count reconciliation', () => { + let bar: { update: sinon.SinonStub; setTotal: sinon.SinonStub; increment: sinon.SinonStub }; + + // Inject a stub bar at the rendering seam so assertions do not depend on the + // load-order-sensitive cli-progress module mock (which does not bind in CI). + function injectBar(processName: string) { + bar = { update: sinon.stub(), setTotal: sinon.stub(), increment: sinon.stub() }; + (progressManager as any).processes.get(processName).progressBar = bar; + } + + beforeEach(() => { + progressManager = new CLIProgressManager({ + enableNestedProgress: true, + moduleName: 'RECONCILE_TEST', + showConsoleLogs: false, + }); + }); + + fancy.it('renders the processed count, not the registered estimate, when fewer items were ticked', () => { + progressManager.addProcess('gf-update', 37); + injectBar('gf-update'); + for (let i = 0; i < 27; i++) { + progressManager.tick(true, `item-${i}`, null, 'gf-update'); + } + + progressManager.completeProcess('gf-update', true); + + expect(bar.setTotal.calledWith(27)).to.equal(true); + expect(bar.update.lastCall.args[0]).to.equal(27); + }); + + fancy.it('renders the full count unchanged when every registered item was ticked', () => { + progressManager.addProcess('entries-create', 59); + injectBar('entries-create'); + for (let i = 0; i < 59; i++) { + progressManager.tick(true, `item-${i}`, null, 'entries-create'); + } + + progressManager.completeProcess('entries-create', true); + + expect(bar.update.lastCall.args[0]).to.equal(59); + }); + + fancy.it('renders the registered total when a process completes with no ticks (skip case)', () => { + progressManager.addProcess('skipped', 5); + injectBar('skipped'); + + progressManager.completeProcess('skipped', true); + + expect(bar.update.lastCall.args[0]).to.equal(5); + }); + + fancy.it('reconciles the denominator for a failed process with partial ticks', () => { + progressManager.addProcess('entries', 59); + injectBar('entries'); + for (let i = 0; i < 38; i++) { + progressManager.tick(true, `ok-${i}`, null, 'entries'); + } + for (let i = 0; i < 3; i++) { + progressManager.tick(false, `err-${i}`, 'boom', 'entries'); + } + + progressManager.completeProcess('entries', false); + + expect(bar.update.lastCall.args[0]).to.equal(41); + }); + }); + describe('Progress Tracking', () => { beforeEach(() => { progressManager = new CLIProgressManager({