okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
e69b399
upstream
1package screen 2 3import ( 4"fmt" 5"testing" 6 7 uv"github.com/charmbracelet/ultraviolet" 8"github.com/charmbracelet/x/ansi" 9) 10 11// mockScreen is a basic screen implementation for testing 12type mockScreen struct { 13* uv.Buffer 14method ansi.Method 15} 16 17func newMockScreen (width ,height int )* mockScreen { 18return & mockScreen { 19Buffer :uv .NewBuffer (width ,height ), 20method :ansi .WcWidth , 21 } 22} 23 24func (m * mockScreen )Bounds () uv.Rectangle { 25return m .Buffer .Bounds () 26} 27 28func (m * mockScreen )CellAt (x ,y int )* uv.Cell { 29return m .Buffer .CellAt (x ,y ) 30} 31 32func (m * mockScreen )SetCell (x ,y int ,c * uv.Cell ) { 33m .Buffer .SetCell (x ,y ,c ) 34} 35 36func (m * mockScreen )WidthMethod () uv.WidthMethod { 37return m .method 38} 39 40// minimalMockScreen is a minimal screen that only implements the required Screen interface 41// and doesn't have any of the optional methods (Clear, Fill, Clone, etc.) 42type minimalMockScreen struct { 43cells [][]uv.Cell 44width int 45height int 46method ansi.Method 47} 48 49func newMinimalMockScreen (width ,height int )* minimalMockScreen { 50cells := make ([][]uv.Cell ,height ) 51for i := range cells { 52cells [i ]= make ([]uv.Cell ,width ) 53for j := range cells [i ] { 54cells [i ][j ]= uv .EmptyCell 55 } 56 } 57return & minimalMockScreen { 58cells :cells , 59width :width , 60height :height , 61method :ansi .WcWidth , 62 } 63} 64 65func (m * minimalMockScreen )Bounds () uv.Rectangle { 66return uv .Rect (0 ,0 ,m .width ,m .height ) 67} 68 69func (m * minimalMockScreen )CellAt (x ,y int )* uv.Cell { 70if x < 0 || x >= m .width || y < 0 || y >= m .height { 71return nil 72 } 73return & m .cells [y ][x ] 74} 75 76func (m * minimalMockScreen )SetCell (x ,y int ,c * uv.Cell ) { 77if x < 0 || x >= m .width || y < 0 || y >= m .height { 78return 79 } 80if c == nil { 81m .cells [y ][x ]= uv .EmptyCell 82 }else { 83m .cells [y ][x ]= * c 84 } 85} 86 87func (m * minimalMockScreen )WidthMethod () uv.WidthMethod { 88return m .method 89} 90 91// nilCellMockScreen is a screen that can return nil for certain cells 92type nilCellMockScreen struct { 93cells map [string ]* uv.Cell 94width int 95height int 96method ansi.Method 97nilPositions map [string ]bool 98} 99 100func newNilCellMockScreen (width ,height int )* nilCellMockScreen { 101return & nilCellMockScreen { 102cells :make (map [string ]* uv.Cell ), 103width :width , 104height :height , 105method :ansi .WcWidth , 106nilPositions :make (map [string ]bool ), 107 } 108} 109 110func (m * nilCellMockScreen )Bounds () uv.Rectangle { 111return uv .Rect (0 ,0 ,m .width ,m .height ) 112} 113 114func (m * nilCellMockScreen )CellAt (x ,y int )* uv.Cell { 115if x < 0 || x >= m .width || y < 0 || y >= m .height { 116return nil 117 } 118key := fmt .Sprintf ("%d,%d" ,x ,y ) 119if m .nilPositions [key ] { 120return nil 121 } 122if cell ,ok := m .cells [key ];ok { 123return cell 124 } 125// Return empty cell by default 126empty := uv .EmptyCell 127return & empty 128} 129 130func (m * nilCellMockScreen )SetCell (x ,y int ,c * uv.Cell ) { 131if x < 0 || x >= m .width || y < 0 || y >= m .height { 132return 133 } 134key := fmt .Sprintf ("%d,%d" ,x ,y ) 135if c == nil { 136m .cells [key ]= & uv .EmptyCell 137 }else { 138cellCopy := * c 139m .cells [key ]= & cellCopy 140 } 141} 142 143func (m * nilCellMockScreen )WidthMethod () uv.WidthMethod { 144return m .method 145} 146 147func (m * nilCellMockScreen )SetNilAt (x ,y int ) { 148key := fmt .Sprintf ("%d,%d" ,x ,y ) 149m .nilPositions [key ]= true 150} 151 152// mockScreenWithClear implements Clear method 153type mockScreenWithClear struct { 154* mockScreen 155clearCalled bool 156} 157 158func (m * mockScreenWithClear )Clear () { 159m .clearCalled = true 160m .Buffer .Clear () 161} 162 163// mockScreenWithClearArea implements ClearArea method 164type mockScreenWithClearArea struct { 165* mockScreen 166clearAreaCalled bool 167lastArea uv.Rectangle 168} 169 170func (m * mockScreenWithClearArea )ClearArea (area uv.Rectangle ) { 171m .clearAreaCalled = true 172m .lastArea = area 173m .Buffer .ClearArea (area ) 174} 175 176// mockScreenWithFill implements Fill method 177type mockScreenWithFill struct { 178* mockScreen 179fillCalled bool 180lastCell * uv.Cell 181} 182 183func (m * mockScreenWithFill )Fill (cell * uv.Cell ) { 184m .fillCalled = true 185m .lastCell = cell 186m .Buffer .Fill (cell ) 187} 188 189// mockScreenWithFillArea implements FillArea method 190type mockScreenWithFillArea struct { 191* mockScreen 192fillAreaCalled bool 193lastCell * uv.Cell 194lastArea uv.Rectangle 195} 196 197func (m * mockScreenWithFillArea )FillArea (cell * uv.Cell ,area uv.Rectangle ) { 198m .fillAreaCalled = true 199m .lastCell = cell 200m .lastArea = area 201m .Buffer .FillArea (cell ,area ) 202} 203 204// mockScreenWithClone implements Clone method 205type mockScreenWithClone struct { 206* mockScreen 207cloneCalled bool 208} 209 210func (m * mockScreenWithClone )Clone ()* uv.Buffer { 211m .cloneCalled = true 212return m .Buffer .Clone () 213} 214 215// mockScreenWithCloneArea implements CloneArea method 216type mockScreenWithCloneArea struct { 217* mockScreen 218cloneAreaCalled bool 219lastArea uv.Rectangle 220} 221 222func (m * mockScreenWithCloneArea )CloneArea (area uv.Rectangle )* uv.Buffer { 223m .cloneAreaCalled = true 224m .lastArea = area 225return m .Buffer .CloneArea (area ) 226} 227 228func TestClear (t * testing.T ) { 229t .Run ("with Clear method" ,func (t * testing.T ) { 230scr := & mockScreenWithClear { 231mockScreen :newMockScreen (10 ,5 ), 232 } 233 234// Set some cells 235testCell := & uv.Cell {Content :"X" ,Width :1 } 236scr .SetCell (0 ,0 ,testCell ) 237scr .SetCell (5 ,2 ,testCell ) 238 239Clear (scr ) 240 241if ! scr .clearCalled { 242t .Error ("Clear method was not called" ) 243 } 244 245// Verify cells are cleared 246if cell := scr .CellAt (0 ,0 );cell != nil && cell .Content != " " { 247t .Errorf ("Cell at (0,0) not cleared, got %v" ,cell ) 248 } 249if cell := scr .CellAt (5 ,2 );cell != nil && cell .Content != " " { 250t .Errorf ("Cell at (5,2) not cleared, got %v" ,cell ) 251 } 252 }) 253 254t .Run ("without Clear method" ,func (t * testing.T ) { 255scr := newMockScreen (10 ,5 ) 256 257// Set some cells 258testCell := & uv.Cell {Content :"X" ,Width :1 } 259scr .SetCell (0 ,0 ,testCell ) 260scr .SetCell (5 ,2 ,testCell ) 261 262Clear (scr ) 263 264// Verify cells are cleared (filled with nil/empty) 265if cell := scr .CellAt (0 ,0 );cell != nil && cell .Content != " " { 266t .Errorf ("Cell at (0,0) not cleared, got %v" ,cell ) 267 } 268if cell := scr .CellAt (5 ,2 );cell != nil && cell .Content != " " { 269t .Errorf ("Cell at (5,2) not cleared, got %v" ,cell ) 270 } 271 }) 272} 273 274func TestClearArea (t * testing.T ) { 275t .Run ("with ClearArea method" ,func (t * testing.T ) { 276scr := & mockScreenWithClearArea { 277mockScreen :newMockScreen (10 ,5 ), 278 } 279 280// Set cells across the screen 281testCell := & uv.Cell {Content :"X" ,Width :1 } 282for y := 0 ;y < 5 ;y ++ { 283for x := 0 ;x < 10 ;x ++ { 284scr .SetCell (x ,y ,testCell ) 285 } 286 } 287 288area := uv .Rect (2 ,1 ,4 ,2 ) 289ClearArea (scr ,area ) 290 291if ! scr .clearAreaCalled { 292t .Error ("ClearArea method was not called" ) 293 } 294 295if scr .lastArea != area { 296t .Errorf ("ClearArea called with wrong area, expected %v, got %v" ,area ,scr .lastArea ) 297 } 298 299// Verify only the area is cleared 300for y := 0 ;y < 5 ;y ++ { 301for x := 0 ;x < 10 ;x ++ { 302cell := scr .CellAt (x ,y ) 303if x >= 2 && x < 6 && y >= 1 && y < 3 { 304// Inside cleared area 305if cell != nil && cell .Content != " " { 306t .Errorf ("Cell at (%d,%d) should be cleared, got %v" ,x ,y ,cell ) 307 } 308 }else { 309// Outside cleared area 310if cell == nil || cell .Content != "X" { 311t .Errorf ("Cell at (%d,%d) should not be cleared, got %v" ,x ,y ,cell ) 312 } 313 } 314 } 315 } 316 }) 317 318t .Run ("without ClearArea method" ,func (t * testing.T ) { 319scr := newMockScreen (10 ,5 ) 320 321// Set cells across the screen 322testCell := & uv.Cell {Content :"X" ,Width :1 } 323for y := 0 ;y < 5 ;y ++ { 324for x := 0 ;x < 10 ;x ++ { 325scr .SetCell (x ,y ,testCell ) 326 } 327 } 328 329area := uv .Rect (2 ,1 ,4 ,2 ) 330ClearArea (scr ,area ) 331 332// Verify only the area is cleared 333for y := 0 ;y < 5 ;y ++ { 334for x := 0 ;x < 10 ;x ++ { 335cell := scr .CellAt (x ,y ) 336if x >= 2 && x < 6 && y >= 1 && y < 3 { 337// Inside cleared area 338if cell != nil && cell .Content != " " { 339t .Errorf ("Cell at (%d,%d) should be cleared, got %v" ,x ,y ,cell ) 340 } 341 }else { 342// Outside cleared area 343if cell == nil || cell .Content != "X" { 344t .Errorf ("Cell at (%d,%d) should not be cleared, got %v" ,x ,y ,cell ) 345 } 346 } 347 } 348 } 349 }) 350} 351 352func TestFill (t * testing.T ) { 353t .Run ("with Fill method" ,func (t * testing.T ) { 354scr := & mockScreenWithFill { 355mockScreen :newMockScreen (10 ,5 ), 356 } 357 358fillCell := & uv.Cell {Content :"F" ,Width :1 } 359Fill (scr ,fillCell ) 360 361if ! scr .fillCalled { 362t .Error ("Fill method was not called" ) 363 } 364 365if scr .lastCell != fillCell { 366t .Errorf ("Fill called with wrong cell, expected %v, got %v" ,fillCell ,scr .lastCell ) 367 } 368 369// Verify all cells are filled 370for y := 0 ;y < 5 ;y ++ { 371for x := 0 ;x < 10 ;x ++ { 372cell := scr .CellAt (x ,y ) 373if cell == nil || cell .Content != "F" { 374t .Errorf ("Cell at (%d,%d) not filled correctly, got %v" ,x ,y ,cell ) 375 } 376 } 377 } 378 }) 379 380t .Run ("without Fill method" ,func (t * testing.T ) { 381scr := newMockScreen (10 ,5 ) 382 383fillCell := & uv.Cell {Content :"F" ,Width :1 } 384Fill (scr ,fillCell ) 385 386// Verify all cells are filled 387for y := 0 ;y < 5 ;y ++ { 388for x := 0 ;x < 10 ;x ++ { 389cell := scr .CellAt (x ,y ) 390if cell == nil || cell .Content != "F" { 391t .Errorf ("Cell at (%d,%d) not filled correctly, got %v" ,x ,y ,cell ) 392 } 393 } 394 } 395 }) 396 397t .Run ("with nil cell" ,func (t * testing.T ) { 398scr := newMockScreen (10 ,5 ) 399 400// Set some cells first 401testCell := & uv.Cell {Content :"X" ,Width :1 } 402scr .SetCell (0 ,0 ,testCell ) 403scr .SetCell (5 ,2 ,testCell ) 404 405Fill (scr ,nil ) 406 407// Verify all cells are cleared (filled with empty) 408for y := 0 ;y < 5 ;y ++ { 409for x := 0 ;x < 10 ;x ++ { 410cell := scr .CellAt (x ,y ) 411if cell != nil && cell .Content != " " { 412t .Errorf ("Cell at (%d,%d) not cleared, got %v" ,x ,y ,cell ) 413 } 414 } 415 } 416 }) 417} 418 419func TestFillArea (t * testing.T ) { 420t .Run ("with FillArea method" ,func (t * testing.T ) { 421scr := & mockScreenWithFillArea { 422mockScreen :newMockScreen (10 ,5 ), 423 } 424 425fillCell := & uv.Cell {Content :"A" ,Width :1 } 426area := uv .Rect (2 ,1 ,4 ,2 ) 427FillArea (scr ,fillCell ,area ) 428 429if ! scr .fillAreaCalled { 430t .Error ("FillArea method was not called" ) 431 } 432 433if scr .lastCell != fillCell { 434t .Errorf ("FillArea called with wrong cell, expected %v, got %v" ,fillCell ,scr .lastCell ) 435 } 436 437if scr .lastArea != area { 438t .Errorf ("FillArea called with wrong area, expected %v, got %v" ,area ,scr .lastArea ) 439 } 440 441// Verify only the area is filled 442for y := 0 ;y < 5 ;y ++ { 443for x := 0 ;x < 10 ;x ++ { 444cell := scr .CellAt (x ,y ) 445if x >= 2 && x < 6 && y >= 1 && y < 3 { 446// Inside filled area 447if cell == nil || cell .Content != "A" { 448t .Errorf ("Cell at (%d,%d) should be filled with 'A', got %v" ,x ,y ,cell ) 449 } 450 } 451 } 452 } 453 }) 454 455t .Run ("with FillArea method and wide cell" ,func (t * testing.T ) { 456scr := & mockScreenWithFillArea { 457mockScreen :newMockScreen (10 ,5 ), 458 } 459 460fillCell := & uv.Cell {Content :"混" ,Width :2 } 461area := uv .Rect (0 ,1 ,4 ,2 ) 462FillArea (scr ,fillCell ,area ) 463 464if ! scr .fillAreaCalled { 465t .Error ("FillArea method was not called" ) 466 } 467 468if scr .lastCell != fillCell { 469t .Errorf ("FillArea called with wrong cell, expected %v, got %v" ,fillCell ,scr .lastCell ) 470 } 471 472if scr .lastArea != area { 473t .Errorf ("FillArea called with wrong area, expected %v, got %v" ,area ,scr .lastArea ) 474 } 475 476// Verify only the area is filled with the wide cell 477for y := 0 ;y < 5 ;y ++ { 478for x := 0 ;x < 10 ;x += fillCell .Width { 479cell := scr .CellAt (x ,y ) 480if x >= 0 && x < 4 && y >= 1 && y < 3 { 481// Inside filled area 482if cell == nil || cell .Content != "混" || cell .Width != 2 { 483t .Errorf ("Cell at (%d,%d) should be filled with '混', got %q" ,x ,y ,cell ) 484 } 485 } 486 } 487 } 488 }) 489 490t .Run ("without FillArea method" ,func (t * testing.T ) { 491scr := newMockScreen (10 ,5 ) 492 493fillCell := & uv.Cell {Content :"B" ,Width :1 } 494area := uv .Rect (2 ,1 ,4 ,2 ) 495FillArea (scr ,fillCell ,area ) 496 497// Verify only the area is filled 498for y := 0 ;y < 5 ;y ++ { 499for x := 0 ;x < 10 ;x ++ { 500cell := scr .CellAt (x ,y ) 501if x >= 2 && x < 6 && y >= 1 && y < 3 { 502// Inside filled area 503if cell == nil || cell .Content != "B" { 504t .Errorf ("Cell at (%d,%d) should be filled with 'B', got %v" ,x ,y ,cell ) 505 } 506 } 507 } 508 } 509 }) 510 511t .Run ("with nil cell" ,func (t * testing.T ) { 512scr := newMockScreen (10 ,5 ) 513 514// Set cells across the screen 515testCell := & uv.Cell {Content :"X" ,Width :1 } 516for y := 0 ;y < 5 ;y ++ { 517for x := 0 ;x < 10 ;x ++ { 518scr .SetCell (x ,y ,testCell ) 519 } 520 } 521 522area := uv .Rect (2 ,1 ,4 ,2 ) 523FillArea (scr ,nil ,area ) 524 525// Verify only the area is cleared 526for y := 0 ;y < 5 ;y ++ { 527for x := 0 ;x < 10 ;x ++ { 528cell := scr .CellAt (x ,y ) 529if x >= 2 && x < 6 && y >= 1 && y < 3 { 530// Inside filled area (should be empty) 531if cell != nil && cell .Content != " " { 532t .Errorf ("Cell at (%d,%d) should be cleared, got %v" ,x ,y ,cell ) 533 } 534 }else { 535// Outside filled area 536if cell == nil || cell .Content != "X" { 537t .Errorf ("Cell at (%d,%d) should not be changed, got %v" ,x ,y ,cell ) 538 } 539 } 540 } 541 } 542 }) 543} 544 545func TestClone (t * testing.T ) { 546t .Run ("with Clone method" ,func (t * testing.T ) { 547scr := & mockScreenWithClone { 548mockScreen :newMockScreen (10 ,5 ), 549 } 550 551// Set some cells 552scr .SetCell (0 ,0 ,& uv.Cell {Content :"A" ,Width :1 }) 553scr .SetCell (5 ,2 ,& uv.Cell {Content :"B" ,Width :1 }) 554scr .SetCell (9 ,4 ,& uv.Cell {Content :"C" ,Width :1 }) 555 556cloned := Clone (scr ) 557 558if ! scr .cloneCalled { 559t .Error ("Clone method was not called" ) 560 } 561 562if cloned == nil { 563t .Fatal ("Clone returned nil" ) 564 } 565 566// Verify cloned buffer has same content 567if cloned .Width ()!= 10 || cloned .Height ()!= 5 { 568t .Errorf ("Cloned buffer has wrong dimensions: %dx%d" ,cloned .Width (),cloned .Height ()) 569 } 570 571// Check specific cells 572if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "A" { 573t .Errorf ("Cloned cell at (0,0) incorrect, got %v" ,cell ) 574 } 575if cell := cloned .CellAt (5 ,2 );cell == nil || cell .Content != "B" { 576t .Errorf ("Cloned cell at (5,2) incorrect, got %v" ,cell ) 577 } 578if cell := cloned .CellAt (9 ,4 );cell == nil || cell .Content != "C" { 579t .Errorf ("Cloned cell at (9,4) incorrect, got %v" ,cell ) 580 } 581 }) 582 583t .Run ("without Clone method" ,func (t * testing.T ) { 584scr := newMockScreen (10 ,5 ) 585 586// Set some cells 587scr .SetCell (0 ,0 ,& uv.Cell {Content :"A" ,Width :1 }) 588scr .SetCell (5 ,2 ,& uv.Cell {Content :"B" ,Width :1 }) 589scr .SetCell (9 ,4 ,& uv.Cell {Content :"C" ,Width :1 }) 590 591cloned := Clone (scr ) 592 593if cloned == nil { 594t .Fatal ("Clone returned nil" ) 595 } 596 597// Verify cloned buffer has same content 598if cloned .Width ()!= 10 || cloned .Height ()!= 5 { 599t .Errorf ("Cloned buffer has wrong dimensions: %dx%d" ,cloned .Width (),cloned .Height ()) 600 } 601 602// Check specific cells 603if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "A" { 604t .Errorf ("Cloned cell at (0,0) incorrect, got %v" ,cell ) 605 } 606if cell := cloned .CellAt (5 ,2 );cell == nil || cell .Content != "B" { 607t .Errorf ("Cloned cell at (5,2) incorrect, got %v" ,cell ) 608 } 609if cell := cloned .CellAt (9 ,4 );cell == nil || cell .Content != "C" { 610t .Errorf ("Cloned cell at (9,4) incorrect, got %v" ,cell ) 611 } 612 }) 613} 614 615func TestCloneArea (t * testing.T ) { 616t .Run ("with CloneArea method" ,func (t * testing.T ) { 617scr := & mockScreenWithCloneArea { 618mockScreen :newMockScreen (10 ,5 ), 619 } 620 621// Set cells across the screen 622for y := 0 ;y < 5 ;y ++ { 623for x := 0 ;x < 10 ;x ++ { 624content := string (rune ('A' + y * 10 + x )) 625scr .SetCell (x ,y ,& uv.Cell {Content :content ,Width :1 }) 626 } 627 } 628 629area := uv .Rect (2 ,1 ,4 ,2 ) 630cloned := CloneArea (scr ,area ) 631 632if ! scr .cloneAreaCalled { 633t .Error ("CloneArea method was not called" ) 634 } 635 636if scr .lastArea != area { 637t .Errorf ("CloneArea called with wrong area, expected %v, got %v" ,area ,scr .lastArea ) 638 } 639 640if cloned == nil { 641t .Fatal ("CloneArea returned nil" ) 642 } 643 644// Verify cloned buffer has correct dimensions 645if cloned .Width ()!= 4 || cloned .Height ()!= 2 { 646t .Errorf ("Cloned buffer has wrong dimensions: %dx%d, expected 4x2" ,cloned .Width (),cloned .Height ()) 647 } 648 649// Verify content matches the cloned area 650for y := 0 ;y < 2 ;y ++ { 651for x := 0 ;x < 4 ;x ++ { 652expectedContent := string (rune ('A' + (y + 1 )* 10 + (x + 2 ))) 653cell := cloned .CellAt (x ,y ) 654if cell == nil || cell .Content != expectedContent { 655t .Errorf ("Cloned cell at (%d,%d) incorrect, expected %s, got %v" ,x ,y ,expectedContent ,cell ) 656 } 657 } 658 } 659 }) 660 661t .Run ("without CloneArea method" ,func (t * testing.T ) { 662scr := newMockScreen (10 ,5 ) 663 664// Set cells across the screen 665for y := 0 ;y < 5 ;y ++ { 666for x := 0 ;x < 10 ;x ++ { 667content := string (rune ('A' + y * 10 + x )) 668scr .SetCell (x ,y ,& uv.Cell {Content :content ,Width :1 }) 669 } 670 } 671 672area := uv .Rect (2 ,1 ,4 ,2 ) 673cloned := CloneArea (scr ,area ) 674 675if cloned == nil { 676t .Fatal ("CloneArea returned nil" ) 677 } 678 679// Verify cloned buffer has correct dimensions 680if cloned .Width ()!= 4 || cloned .Height ()!= 2 { 681t .Errorf ("Cloned buffer has wrong dimensions: %dx%d, expected 4x2" ,cloned .Width (),cloned .Height ()) 682 } 683 684// Verify content matches the cloned area 685for y := 0 ;y < 2 ;y ++ { 686for x := 0 ;x < 4 ;x ++ { 687expectedContent := string (rune ('A' + (y + 1 )* 10 + (x + 2 ))) 688cell := cloned .CellAt (x ,y ) 689if cell == nil || cell .Content != expectedContent { 690t .Errorf ("Cloned cell at (%d,%d) incorrect, expected %s, got %v" ,x ,y ,expectedContent ,cell ) 691 } 692 } 693 } 694 }) 695 696t .Run ("with zero cells" ,func (t * testing.T ) { 697scr := newMockScreen (10 ,5 ) 698 699// Set some cells but leave some as zero 700scr .SetCell (2 ,1 ,& uv.Cell {Content :"A" ,Width :1 }) 701scr .SetCell (4 ,2 ,& uv.Cell {Content :"B" ,Width :1 }) 702// Leave (3,1) and (5,2) as zero cells 703 704area := uv .Rect (2 ,1 ,4 ,2 ) 705cloned := CloneArea (scr ,area ) 706 707if cloned == nil { 708t .Fatal ("CloneArea returned nil" ) 709 } 710 711// Check that non-zero cells are cloned 712if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "A" { 713t .Errorf ("Cell at (0,0) should be 'A', got %v" ,cell ) 714 } 715if cell := cloned .CellAt (2 ,1 );cell == nil || cell .Content != "B" { 716t .Errorf ("Cell at (2,1) should be 'B', got %v" ,cell ) 717 } 718 719// Check that zero cells are not cloned (should be empty) 720if cell := cloned .CellAt (1 ,0 );cell != nil && ! cell .IsZero ()&& cell .Content != " " { 721t .Errorf ("Cell at (1,0) should be zero or empty, got %v" ,cell ) 722 } 723if cell := cloned .CellAt (3 ,1 );cell != nil && ! cell .IsZero ()&& cell .Content != " " { 724t .Errorf ("Cell at (3,1) should be zero or empty, got %v" ,cell ) 725 } 726 }) 727} 728 729func TestScreenBufferIntegration (t * testing.T ) { 730t .Run ("using ScreenBuffer" ,func (t * testing.T ) { 731scr := uv .NewScreenBuffer (10 ,5 ) 732 733// Test Clear 734scr .SetCell (0 ,0 ,& uv.Cell {Content :"X" ,Width :1 }) 735Clear (scr ) 736if cell := scr .CellAt (0 ,0 );cell != nil && cell .Content != " " { 737t .Errorf ("Clear failed, cell at (0,0) is %v" ,cell ) 738 } 739 740// Test Fill 741fillCell := & uv.Cell {Content :"F" ,Width :1 } 742Fill (scr ,fillCell ) 743for y := 0 ;y < 5 ;y ++ { 744for x := 0 ;x < 10 ;x ++ { 745if cell := scr .CellAt (x ,y );cell == nil || cell .Content != "F" { 746t .Errorf ("Fill failed at (%d,%d), got %v" ,x ,y ,cell ) 747 } 748 } 749 } 750 751// Test ClearArea 752area := uv .Rect (2 ,1 ,3 ,2 ) 753ClearArea (scr ,area ) 754for y := 1 ;y < 3 ;y ++ { 755for x := 2 ;x < 5 ;x ++ { 756if cell := scr .CellAt (x ,y );cell != nil && cell .Content != " " { 757t .Errorf ("ClearArea failed at (%d,%d), got %v" ,x ,y ,cell ) 758 } 759 } 760 } 761 762// Test FillArea 763fillCell2 := & uv.Cell {Content :"A" ,Width :1 } 764area2 := uv .Rect (1 ,1 ,2 ,2 ) 765FillArea (scr ,fillCell2 ,area2 ) 766for y := 1 ;y < 3 ;y ++ { 767for x := 1 ;x < 3 ;x ++ { 768if cell := scr .CellAt (x ,y );cell == nil || cell .Content != "A" { 769t .Errorf ("FillArea failed at (%d,%d), got %v" ,x ,y ,cell ) 770 } 771 } 772 } 773 774// Test Clone 775cloned := Clone (scr ) 776if cloned .Width ()!= 10 || cloned .Height ()!= 5 { 777t .Errorf ("Clone dimensions wrong: %dx%d" ,cloned .Width (),cloned .Height ()) 778 } 779 780// Test CloneArea 781area3 := uv .Rect (0 ,0 ,3 ,3 ) 782clonedArea := CloneArea (scr ,area3 ) 783if clonedArea .Width ()!= 3 || clonedArea .Height ()!= 3 { 784t .Errorf ("CloneArea dimensions wrong: %dx%d" ,clonedArea .Width (),clonedArea .Height ()) 785 } 786 }) 787} 788 789func TestEdgeCases (t * testing.T ) { 790t .Run ("empty screen" ,func (t * testing.T ) { 791scr := newMockScreen (0 ,0 ) 792 793// These should not panic 794Clear (scr ) 795Fill (scr ,& uv.Cell {Content :"X" ,Width :1 }) 796ClearArea (scr ,uv .Rect (0 ,0 ,1 ,1 )) 797FillArea (scr ,& uv.Cell {Content :"X" ,Width :1 },uv .Rect (0 ,0 ,1 ,1 )) 798Clone (scr ) 799CloneArea (scr ,uv .Rect (0 ,0 ,1 ,1 )) 800 }) 801 802t .Run ("wide cells" ,func (t * testing.T ) { 803scr := uv .NewScreenBuffer (10 ,5 ) 804 805// Test with wide cell (e.g., emoji or CJK character) 806wideCell := & uv.Cell {Content :"😀" ,Width :2 } 807scr .SetCell (0 ,0 ,wideCell ) 808 809t .Logf ("Set wide cell at (0,0): %+v" ,scr .CellAt (0 ,0 )) 810 811cloned := Clone (scr ) 812t .Logf ("Cloned cell at (0,0): %+v" ,cloned .CellAt (0 ,0 )) 813 814if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "😀" || cell .Width != 2 { 815t .Errorf ("Wide cell not cloned correctly, got %#v" ,cell ) 816 } 817 818// Test FillArea with wide cell 819FillArea (scr ,wideCell ,uv .Rect (0 ,1 ,4 ,1 )) 820for x := 0 ;x < 4 ;x += 2 { 821if cell := scr .CellAt (x ,1 );cell == nil || cell .Content != "😀" || cell .Width != 2 { 822t .Errorf ("Wide cell at (%d,1) not filled correctly, got %#v" ,x ,cell ) 823 } 824 } 825 }) 826 827t .Run ("styled cells" ,func (t * testing.T ) { 828scr := uv .NewScreenBuffer (10 ,5 ) 829 830// Test with styled cell 831styledCell := & uv.Cell { 832Content :"S" , 833Width :1 , 834Style : uv.Style {Attrs :uv .AttrBold | uv .AttrItalic }, 835 } 836scr .SetCell (0 ,0 ,styledCell ) 837 838cloned := Clone (scr ) 839if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "S" || (cell .Style .Attrs & uv .AttrBold == 0 )|| (cell .Style .Attrs & uv .AttrItalic == 0 ) { 840t .Errorf ("Styled cell not cloned correctly, got %v" ,cell ) 841 } 842 }) 843 844t .Run ("cells with links" ,func (t * testing.T ) { 845scr := uv .NewScreenBuffer (10 ,5 ) 846 847// Test with cell containing hyperlink 848linkedCell := & uv.Cell { 849Content :"L" , 850Width :1 , 851Link :uv .NewLink ("https://example.com" ,"id=test" ), 852 } 853scr .SetCell (0 ,0 ,linkedCell ) 854 855cloned := Clone (scr ) 856if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "L" || cell .Link .URL != "https://example.com" { 857t .Errorf ("Cell with link not cloned correctly, got %v" ,cell ) 858 } 859 }) 860} 861 862func TestMinimalScreenFallbacks (t * testing.T ) { 863t .Run ("Clear fallback" ,func (t * testing.T ) { 864scr := newMinimalMockScreen (5 ,3 ) 865 866// Set some cells 867testCell := & uv.Cell {Content :"X" ,Width :1 } 868scr .SetCell (0 ,0 ,testCell ) 869scr .SetCell (2 ,1 ,testCell ) 870scr .SetCell (4 ,2 ,testCell ) 871 872// Clear should use Fill fallback 873Clear (scr ) 874 875// Verify all cells are cleared 876for y := 0 ;y < 3 ;y ++ { 877for x := 0 ;x < 5 ;x ++ { 878cell := scr .CellAt (x ,y ) 879if cell != nil && cell .Content != " " { 880t .Errorf ("Cell at (%d,%d) not cleared, got %v" ,x ,y ,cell ) 881 } 882 } 883 } 884 }) 885 886t .Run ("ClearArea fallback" ,func (t * testing.T ) { 887scr := newMinimalMockScreen (5 ,3 ) 888 889// Set cells across the screen 890testCell := & uv.Cell {Content :"X" ,Width :1 } 891for y := 0 ;y < 3 ;y ++ { 892for x := 0 ;x < 5 ;x ++ { 893scr .SetCell (x ,y ,testCell ) 894 } 895 } 896 897// ClearArea should use FillArea fallback 898area := uv .Rect (1 ,0 ,3 ,2 ) 899ClearArea (scr ,area ) 900 901// Verify only the area is cleared 902for y := 0 ;y < 3 ;y ++ { 903for x := 0 ;x < 5 ;x ++ { 904cell := scr .CellAt (x ,y ) 905if x >= 1 && x < 4 && y >= 0 && y < 2 { 906// Inside cleared area 907if cell != nil && cell .Content != " " { 908t .Errorf ("Cell at (%d,%d) should be cleared, got %v" ,x ,y ,cell ) 909 } 910 }else { 911// Outside cleared area 912if cell == nil || cell .Content != "X" { 913t .Errorf ("Cell at (%d,%d) should not be cleared, got %v" ,x ,y ,cell ) 914 } 915 } 916 } 917 } 918 }) 919 920t .Run ("Fill fallback" ,func (t * testing.T ) { 921scr := newMinimalMockScreen (5 ,3 ) 922 923fillCell := & uv.Cell {Content :"F" ,Width :1 } 924// Fill should use FillArea fallback 925Fill (scr ,fillCell ) 926 927// Verify all cells are filled 928for y := 0 ;y < 3 ;y ++ { 929for x := 0 ;x < 5 ;x ++ { 930cell := scr .CellAt (x ,y ) 931if cell == nil || cell .Content != "F" { 932t .Errorf ("Cell at (%d,%d) not filled correctly, got %v" ,x ,y ,cell ) 933 } 934 } 935 } 936 }) 937 938t .Run ("FillArea fallback loop" ,func (t * testing.T ) { 939scr := newMinimalMockScreen (5 ,3 ) 940 941fillCell := & uv.Cell {Content :"A" ,Width :1 } 942area := uv .Rect (1 ,1 ,2 ,1 ) 943// FillArea should use the loop fallback 944FillArea (scr ,fillCell ,area ) 945 946// Verify only the area is filled 947for y := 0 ;y < 3 ;y ++ { 948for x := 0 ;x < 5 ;x ++ { 949cell := scr .CellAt (x ,y ) 950if x >= 1 && x < 3 && y >= 1 && y < 2 { 951// Inside filled area 952if cell == nil || cell .Content != "A" { 953t .Errorf ("Cell at (%d,%d) should be filled with 'A', got %v" ,x ,y ,cell ) 954 } 955 }else { 956// Outside filled area (should be empty) 957if cell != nil && cell .Content != " " { 958t .Errorf ("Cell at (%d,%d) should be empty, got %v" ,x ,y ,cell ) 959 } 960 } 961 } 962 } 963 }) 964 965t .Run ("Clone fallback" ,func (t * testing.T ) { 966scr := newMinimalMockScreen (5 ,3 ) 967 968// Set some cells 969scr .SetCell (0 ,0 ,& uv.Cell {Content :"A" ,Width :1 }) 970scr .SetCell (2 ,1 ,& uv.Cell {Content :"B" ,Width :1 }) 971scr .SetCell (4 ,2 ,& uv.Cell {Content :"C" ,Width :1 }) 972 973// Clone should use CloneArea fallback 974cloned := Clone (scr ) 975 976if cloned == nil { 977t .Fatal ("Clone returned nil" ) 978 } 979 980// Verify cloned buffer has same dimensions 981if cloned .Width ()!= 5 || cloned .Height ()!= 3 { 982t .Errorf ("Cloned buffer has wrong dimensions: %dx%d" ,cloned .Width (),cloned .Height ()) 983 } 984 985// Check specific cells 986if cell := cloned .CellAt (0 ,0 );cell == nil || cell .Content != "A" { 987t .Errorf ("Cloned cell at (0,0) incorrect, got %v" ,cell ) 988 } 989if cell := cloned .CellAt (2 ,1 );cell == nil || cell .Content != "B" { 990t .Errorf ("Cloned cell at (2,1) incorrect, got %v" ,cell ) 991 } 992if cell := cloned .CellAt (4 ,2 );cell == nil || cell .Content != "C" { 993t .Errorf ("Cloned cell at (4,2) incorrect, got %v" ,cell ) 994 } 995 }) 996 997t .Run ("CloneArea fallback loop" ,func (t * testing.T ) { 998scr := newMinimalMockScreen (5 ,3 ) 999 1000// Set cells across the screen 1001for y := 0 ;y < 3 ;y ++ { 1002for x := 0 ;x < 5 ;x ++ { 1003content := string (rune ('A' + y * 5 + x )) 1004scr .SetCell (x ,y ,& uv.Cell {Content :content ,Width :1 }) 1005 } 1006 } 1007 1008// CloneArea should use the loop fallback 1009area := uv .Rect (1 ,0 ,3 ,2 ) 1010cloned := CloneArea (scr ,area ) 1011 1012if cloned == nil { 1013t .Fatal ("CloneArea returned nil" ) 1014 } 1015 1016// Verify cloned buffer has correct dimensions 1017if cloned .Width ()!= 3 || cloned .Height ()!= 2 { 1018t .Errorf ("Cloned buffer has wrong dimensions: %dx%d, expected 3x2" ,cloned .Width (),cloned .Height ()) 1019 } 1020 1021// Verify content matches the cloned area 1022for y := 0 ;y < 2 ;y ++ { 1023for x := 0 ;x < 3 ;x ++ { 1024expectedContent := string (rune ('A' + y * 5 + (x + 1 ))) 1025cell := cloned .CellAt (x ,y ) 1026if cell == nil || cell .Content != expectedContent { 1027t .Errorf ("Cloned cell at (%d,%d) incorrect, expected %s, got %v" ,x ,y ,expectedContent ,cell ) 1028 } 1029 } 1030 } 1031 }) 1032 1033t .Run ("CloneArea with nil and zero cells" ,func (t * testing.T ) { 1034scr := newMinimalMockScreen (5 ,3 ) 1035 1036// Set some cells but leave some as zero/empty 1037scr .SetCell (1 ,0 ,& uv.Cell {Content :"A" ,Width :1 }) 1038scr .SetCell (3 ,1 ,& uv.Cell {Content :"B" ,Width :1 }) 1039// Set a zero cell explicitly 1040scr .SetCell (2 ,0 ,& uv.Cell {}) 1041// Leave other cells as empty 1042 1043area := uv .Rect (0 ,0 ,5 ,2 ) 1044cloned := CloneArea (scr ,area ) 1045 1046if cloned == nil { 1047t .Fatal ("CloneArea returned nil" ) 1048 } 1049 1050// Check that non-zero cells are cloned 1051if cell := cloned .CellAt (1 ,0 );cell == nil || cell .Content != "A" { 1052t .Errorf ("Cell at (1,0) should be 'A', got %v" ,cell ) 1053 } 1054if cell := cloned .CellAt (3 ,1 );cell == nil || cell .Content != "B" { 1055t .Errorf ("Cell at (3,1) should be 'B', got %v" ,cell ) 1056 } 1057 1058// Check that empty cells remain empty 1059if cell := cloned .CellAt (0 ,0 );cell != nil && ! cell .IsZero ()&& cell .Content != " " { 1060t .Errorf ("Cell at (0,0) should be empty, got %v" ,cell ) 1061 } 1062if cell := cloned .CellAt (2 ,0 );cell != nil && ! cell .IsZero ()&& cell .Content != " " { 1063t .Errorf ("Cell at (2,0) should be empty, got %v" ,cell ) 1064 } 1065if cell := cloned .CellAt (2 ,1 );cell != nil && ! cell .IsZero ()&& cell .Content != " " { 1066t .Errorf ("Cell at (2,1) should be empty, got %v" ,cell ) 1067 } 1068 }) 1069 1070t .Run ("CloneArea with nil cells" ,func (t * testing.T ) { 1071scr := newNilCellMockScreen (5 ,3 ) 1072 1073// Set some cells 1074scr .SetCell (1 ,0 ,& uv.Cell {Content :"A" ,Width :1 }) 1075scr .SetCell (3 ,1 ,& uv.Cell {Content :"B" ,Width :1 }) 1076 1077// Make position (2, 1) return nil 1078scr .SetNilAt (2 ,1 ) 1079 1080area := uv .Rect (0 ,0 ,5 ,2 ) 1081cloned := CloneArea (scr ,area ) 1082 1083if cloned == nil { 1084t .Fatal ("CloneArea returned nil" ) 1085 } 1086 1087// Check that non-nil cells are cloned 1088if cell := cloned .CellAt (1 ,0 );cell == nil || cell .Content != "A" { 1089t .Errorf ("Cell at (1,0) should be 'A', got %v" ,cell ) 1090 } 1091if cell := cloned .CellAt (3 ,1 );cell == nil || cell .Content != "B" { 1092t .Errorf ("Cell at (3,1) should be 'B', got %v" ,cell ) 1093 } 1094 1095// Check that nil cell position remains empty in cloned buffer 1096if cell := cloned .CellAt (2 ,1 );cell != nil && ! cell .IsZero ()&& cell .Content != " " { 1097t .Errorf ("Cell at (2,1) should be empty (was nil), got %v" ,cell ) 1098 } 1099 }) 1100} 1101 1102func BenchmarkClear (b * testing.B ) { 1103scr := uv .NewScreenBuffer (80 ,24 ) 1104b .ResetTimer () 1105for i := 0 ;i < b .N ;i ++ { 1106Clear (scr ) 1107 } 1108} 1109 1110func BenchmarkFill (b * testing.B ) { 1111scr := uv .NewScreenBuffer (80 ,24 ) 1112cell := & uv.Cell {Content :"X" ,Width :1 } 1113b .ResetTimer () 1114for i := 0 ;i < b .N ;i ++ { 1115Fill (scr ,cell ) 1116 } 1117} 1118 1119func BenchmarkClone (b * testing.B ) { 1120scr := uv .NewScreenBuffer (80 ,24 ) 1121// Fill with some content 1122for y := 0 ;y < 24 ;y ++ { 1123for x := 0 ;x < 80 ;x ++ { 1124scr .SetCell (x ,y ,& uv.Cell {Content :"X" ,Width :1 }) 1125 } 1126 } 1127b .ResetTimer () 1128for i := 0 ;i < b .N ;i ++ { 1129Clone (scr ) 1130 } 1131} 1132 1133func BenchmarkCloneArea (b * testing.B ) { 1134scr := uv .NewScreenBuffer (80 ,24 ) 1135// Fill with some content 1136for y := 0 ;y < 24 ;y ++ { 1137for x := 0 ;x < 80 ;x ++ { 1138scr .SetCell (x ,y ,& uv.Cell {Content :"X" ,Width :1 }) 1139 } 1140 } 1141area := uv .Rect (10 ,5 ,20 ,10 ) 1142b .ResetTimer () 1143for i := 0 ;i < b .N ;i ++ { 1144CloneArea (scr ,area ) 1145 } 1146}