|
4 | 4 | stripLineNumbers, |
5 | 5 | truncateOutput, |
6 | 6 | applyRunLengthEncoding, |
| 7 | + processCarriageReturns, |
7 | 8 | } from "../extract-text" |
8 | 9 |
|
9 | 10 | describe("addLineNumbers", () => { |
@@ -261,3 +262,166 @@ describe("applyRunLengthEncoding", () => { |
261 | 262 | expect(applyRunLengthEncoding(input)).toBe(input) |
262 | 263 | }) |
263 | 264 | }) |
| 265 | + |
| 266 | +describe("processCarriageReturns", () => { |
| 267 | + it("should return original input if no carriage returns present", () => { |
| 268 | + const input = "Line 1\nLine 2\nLine 3" |
| 269 | + expect(processCarriageReturns(input)).toBe(input) |
| 270 | + }) |
| 271 | + |
| 272 | + it("should process basic progress bar with carriage returns", () => { |
| 273 | + const input = "Progress: [===>---------] 30%\rProgress: [======>------] 60%\rProgress: [==========>] 100%" |
| 274 | + const expected = "Progress: [==========>] 100%%" |
| 275 | + expect(processCarriageReturns(input)).toBe(expected) |
| 276 | + }) |
| 277 | + |
| 278 | + it("should handle multi-line outputs with carriage returns", () => { |
| 279 | + const input = "Line 1\rUpdated Line 1\nLine 2\rUpdated Line 2\rFinal Line 2" |
| 280 | + const expected = "Updated Line 1\nFinal Line 2 2" |
| 281 | + expect(processCarriageReturns(input)).toBe(expected) |
| 282 | + }) |
| 283 | + |
| 284 | + it("should handle carriage returns at end of line", () => { |
| 285 | + // A carriage return at the end of a line should be treated as if the cursor is at the start |
| 286 | + // with no content following it, so we keep the existing content |
| 287 | + const input = "Initial text\rReplacement text\r" |
| 288 | + // Depending on terminal behavior: |
| 289 | + // Option 1: If last CR is ignored because nothing follows it to replace text |
| 290 | + const expected = "Replacement text" |
| 291 | + expect(processCarriageReturns(input)).toBe(expected) |
| 292 | + }) |
| 293 | + |
| 294 | + // Additional test to clarify behavior with a terminal-like example |
| 295 | + it("should handle carriage returns in a way that matches terminal behavior", () => { |
| 296 | + // In a real terminal: |
| 297 | + // 1. "Hello" is printed |
| 298 | + // 2. CR moves cursor to start of line |
| 299 | + // 3. "World" overwrites, becoming "World" |
| 300 | + // 4. CR moves cursor to start again |
| 301 | + // 5. Nothing follows, so the line remains "World" (cursor just sitting at start) |
| 302 | + const input = "Hello\rWorld\r" |
| 303 | + const expected = "World" |
| 304 | + expect(processCarriageReturns(input)).toBe(expected) |
| 305 | + |
| 306 | + // Same principle applies to CR+NL |
| 307 | + // 1. "Line1" is printed |
| 308 | + // 2. CR moves cursor to start |
| 309 | + // 3. NL moves to next line, so the line remains "Line1" |
| 310 | + expect(processCarriageReturns("Line1\r\n")).toBe("Line1\n") |
| 311 | + }) |
| 312 | + |
| 313 | + it("should preserve lines without carriage returns", () => { |
| 314 | + const input = "Line 1\nLine 2\rUpdated Line 2\nLine 3" |
| 315 | + const expected = "Line 1\nUpdated Line 2\nLine 3" |
| 316 | + expect(processCarriageReturns(input)).toBe(expected) |
| 317 | + }) |
| 318 | + |
| 319 | + it("should handle complex tqdm-like progress bars", () => { |
| 320 | + const input = |
| 321 | + "10%|██ | 10/100 [00:01<00:09, 10.00it/s]\r20%|████ | 20/100 [00:02<00:08, 10.00it/s]\r100%|██████████| 100/100 [00:10<00:00, 10.00it/s]" |
| 322 | + const expected = "100%|██████████| 100/100 [00:10<00:00, 10.00it/s]" |
| 323 | + expect(processCarriageReturns(input)).toBe(expected) |
| 324 | + }) |
| 325 | + |
| 326 | + it("should handle ANSI escape sequences", () => { |
| 327 | + const input = "\x1b]633;C\x07Loading\rLoading.\rLoading..\rLoading...\x1b]633;D\x07" |
| 328 | + const expected = "Loading...\x1b]633;D\x07" |
| 329 | + expect(processCarriageReturns(input)).toBe(expected) |
| 330 | + }) |
| 331 | + |
| 332 | + it("should handle mixed content with carriage returns and newlines", () => { |
| 333 | + const input = |
| 334 | + "Step 1: Starting\rStep 1: In progress\rStep 1: Done\nStep 2: Starting\rStep 2: In progress\rStep 2: Done" |
| 335 | + const expected = "Step 1: Donerogress\nStep 2: Donerogress" |
| 336 | + expect(processCarriageReturns(input)).toBe(expected) |
| 337 | + }) |
| 338 | + |
| 339 | + it("should handle empty input", () => { |
| 340 | + expect(processCarriageReturns("")).toBe("") |
| 341 | + }) |
| 342 | + |
| 343 | + it("should handle large number of carriage returns efficiently", () => { |
| 344 | + // Create a string with many carriage returns |
| 345 | + let input = "" |
| 346 | + for (let i = 0; i < 10000; i++) { |
| 347 | + input += `Progress: ${i / 100}%\r` |
| 348 | + } |
| 349 | + input += "Progress: 100%" |
| 350 | + |
| 351 | + const expected = "Progress: 100%9%" |
| 352 | + expect(processCarriageReturns(input)).toBe(expected) |
| 353 | + }) |
| 354 | + |
| 355 | + // Additional edge cases to stress test processCarriageReturns |
| 356 | + it("should handle consecutive carriage returns", () => { |
| 357 | + const input = "Initial\r\r\r\rFinal" |
| 358 | + const expected = "Finalal" |
| 359 | + expect(processCarriageReturns(input)).toBe(expected) |
| 360 | + }) |
| 361 | + |
| 362 | + it("should handle carriage returns at the start of a line", () => { |
| 363 | + const input = "\rText after carriage return" |
| 364 | + const expected = "Text after carriage return" |
| 365 | + expect(processCarriageReturns(input)).toBe(expected) |
| 366 | + }) |
| 367 | + |
| 368 | + it("should handle only carriage returns", () => { |
| 369 | + const input = "\r\r\r\r" |
| 370 | + const expected = "" |
| 371 | + expect(processCarriageReturns(input)).toBe(expected) |
| 372 | + }) |
| 373 | + |
| 374 | + it("should handle carriage returns with empty strings between them", () => { |
| 375 | + const input = "Start\r\r\r\r\rEnd" |
| 376 | + const expected = "Endrt" |
| 377 | + expect(processCarriageReturns(input)).toBe(expected) |
| 378 | + }) |
| 379 | + |
| 380 | + it("should handle multiline with carriage returns at different positions", () => { |
| 381 | + const input = "Line1\rLine1Updated\nLine2\nLine3\rLine3Updated\rLine3Final\nLine4" |
| 382 | + const expected = "Line1Updated\nLine2\nLine3Finaled\nLine4" |
| 383 | + expect(processCarriageReturns(input)).toBe(expected) |
| 384 | + }) |
| 385 | + |
| 386 | + it("should handle carriage returns with special characters", () => { |
| 387 | + const input = "Line with 🚀 emoji\rUpdated with 🔥 emoji" |
| 388 | + const expected = "Updated with 🔥 emoji" |
| 389 | + expect(processCarriageReturns(input)).toBe(expected) |
| 390 | + }) |
| 391 | + |
| 392 | + it("should correctly handle multiple consecutive newlines with carriage returns", () => { |
| 393 | + const input = "Line with not a emoji\rLine with 🔥 emoji" |
| 394 | + const expected = "Line with 🔥 emojioji" |
| 395 | + expect(processCarriageReturns(input)).toBe(expected) |
| 396 | + }) |
| 397 | + |
| 398 | + it("should handle carriage returns in the middle of non-ASCII text", () => { |
| 399 | + const input = "你好世界啊\r你好地球" |
| 400 | + const expected = "你好地球啊" |
| 401 | + expect(processCarriageReturns(input)).toBe(expected) |
| 402 | + }) |
| 403 | + |
| 404 | + it("should correctly handle complex patterns of alternating carriage returns and newlines", () => { |
| 405 | + // Break down the example: |
| 406 | + // 1. "Line1" + CR + NL: CR moves cursor to start of line, NL moves to next line, preserving "Line1" |
| 407 | + // 2. "Line2" + CR: CR moves cursor to start of line |
| 408 | + // 3. "Line2Updated" overwrites "Line2" |
| 409 | + // 4. NL: moves to next line |
| 410 | + // 5. "Line3" + CR + NL: CR moves cursor to start, NL moves to next line, preserving "Line3" |
| 411 | + const input = "Line1\r\nLine2\rLine2Updated\nLine3\r\n" |
| 412 | + const expected = "Line1\nLine2Updated\nLine3\n" |
| 413 | + expect(processCarriageReturns(input)).toBe(expected) |
| 414 | + }) |
| 415 | + |
| 416 | + it("should handle partial overwrites with carriage returns", () => { |
| 417 | + // In this case: |
| 418 | + // 1. "Initial text" is printed |
| 419 | + // 2. CR moves cursor to start of line |
| 420 | + // 3. "next" is printed, overwriting only the first 4 chars |
| 421 | + // 4. CR moves cursor to start, but nothing follows |
| 422 | + // Final result should be "nextial text" (first 4 chars overwritten) |
| 423 | + const input = "Initial text\rnext\r" |
| 424 | + const expected = "nextial text" |
| 425 | + expect(processCarriageReturns(input)).toBe(expected) |
| 426 | + }) |
| 427 | +}) |
0 commit comments