okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Ayman Bagabaschore(screen): test: add test for FillArea with wide celle69b399

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