From ce7736105747f48debb37ca1999b562b4bfae23c Mon Sep 17 00:00:00 2001 From: Manoj Mahapatra Date: Thu, 2 Apr 2026 13:05:52 -0700 Subject: [PATCH] doc: Document plain clear-on finish behavior (#1379) - Clarified the `.plain` progress contract. This also documents how `clearOnFinish` interacts with plain output. Added test coverage for the current behavior. --- Sources/TerminalProgress/ProgressConfig.swift | 4 ++- .../ProgressBarTests.swift | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Sources/TerminalProgress/ProgressConfig.swift b/Sources/TerminalProgress/ProgressConfig.swift index 6346f192..1e2e8a55 100644 --- a/Sources/TerminalProgress/ProgressConfig.swift +++ b/Sources/TerminalProgress/ProgressConfig.swift @@ -62,6 +62,7 @@ public struct ProgressConfig: Sendable { /// The theme of the progress bar. public let theme: ProgressTheme /// The flag indicating whether to clear the progress bar before resetting the cursor. + /// In `.plain` mode, `finish()` only emits a final line when this value is `false`. public let clearOnFinish: Bool /// The flag indicating whether to update the progress bar. public let disableProgressUpdates: Bool @@ -89,7 +90,8 @@ public struct ProgressConfig: Sendable { /// - totalSize: The initial total size of the progress bar. The default value is `nil`. /// - width: The width of the progress bar in characters. The default value is `120`. /// - theme: The theme of the progress bar. The default value is `nil`. - /// - clearOnFinish: The flag indicating whether to clear the progress bar before resetting the cursor. The default is `true`. + /// - clearOnFinish: The flag indicating whether to clear the progress bar before resetting the cursor. In `.plain` mode, + /// `finish()` only emits a final line when this value is `false`. The default is `true`. /// - disableProgressUpdates: The flag indicating whether to update the progress bar. The default is `false`. /// - outputMode: The output mode for progress rendering. The default is `.ansi`. public init( diff --git a/Tests/TerminalProgressTests/ProgressBarTests.swift b/Tests/TerminalProgressTests/ProgressBarTests.swift index 932c578d..6fdd3129 100644 --- a/Tests/TerminalProgressTests/ProgressBarTests.swift +++ b/Tests/TerminalProgressTests/ProgressBarTests.swift @@ -880,7 +880,7 @@ final class ProgressBarTests: XCTestCase { try pipe.fileHandleForWriting.close() let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) ?? "" + let output = String(decoding: data, as: UTF8.self) let lines = output.components(separatedBy: "\n").filter { !$0.isEmpty } // Expect exactly 2 lines: one from render, one from finish XCTAssertEqual(lines.count, 2) @@ -903,7 +903,7 @@ final class ProgressBarTests: XCTestCase { try pipe.fileHandleForWriting.close() let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) ?? "" + let output = String(decoding: data, as: UTF8.self) XCTAssertFalse(output.contains("\u{001B}")) } @@ -922,12 +922,32 @@ final class ProgressBarTests: XCTestCase { try pipe.fileHandleForWriting.close() let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) ?? "" + let output = String(decoding: data, as: UTF8.self) // Plain mode should use newlines, not carriage returns XCTAssertFalse(output.contains("\r")) XCTAssertTrue(output.contains("\n")) } + func testPlainModeDefaultClearOnFinishOmitsFinalLine() async throws { + let pipe = Pipe() + let config = try ProgressConfig( + terminal: pipe.fileHandleForWriting, + description: "Task", + showSpinner: false, + outputMode: .plain + ) + let progress = ProgressBar(config: config) + progress.render(force: true) + progress.finish() + try pipe.fileHandleForWriting.close() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(decoding: data, as: UTF8.self) + let lines = output.components(separatedBy: "\n").filter { !$0.isEmpty } + XCTAssertEqual(lines.count, 1) + XCTAssertEqual(lines.first, "Task [0s]") + } + func testOutputModeDefaultIsAnsi() async throws { let config = try ProgressConfig(description: "Task") XCTAssertEqual(config.outputMode, .ansi)