package tui import ( "strings" "testing" ) func TestVTRender(t *testing.T) { t.Run("layout absoluto basico A y B separados por movimiento de cursor", func(t *testing.T) { // ESC[1;5H mueve el cursor a fila 1 columna 5 (1-indexed). // Resultado esperado: 'A' en col 1, espacios, 'B' en col 5. out := VTRender("A\x1b[1;5HB", 2, 10) lines := strings.Split(out, "\n") if len(lines) == 0 { t.Fatalf("resultado vacio") } first := lines[0] if len(first) < 5 { t.Fatalf("linea demasiado corta: %q", first) } if first[0] != 'A' { t.Errorf("esperaba 'A' en columna 0, got %q en linea %q", string(first[0]), first) } if first[4] != 'B' { t.Errorf("esperaba 'B' en columna 4 (0-indexed), got %q en linea %q", string(first[4]), first) } // Verificar que hay espacios entre A y B (no están pegadas). if strings.Contains(first, "AB") { t.Errorf("A y B estan pegadas en %q, deberían estar separadas", first) } }) t.Run("dos palabras separadas por movimiento de columna no aparecen pegadas", func(t *testing.T) { // ESC[10G mueve el cursor a la columna 10 (1-indexed) de la línea actual. out := VTRender("foo\x1b[10Gbar", 2, 20) lines := strings.Split(out, "\n") if len(lines) == 0 { t.Fatalf("resultado vacio") } first := lines[0] if strings.Contains(first, "foobar") { t.Errorf("foo y bar estan pegadas: %q — esperaba espacios entre ellas", first) } if !strings.Contains(first, "foo") { t.Errorf("no encontre 'foo' en %q", first) } if !strings.Contains(first, "bar") { t.Errorf("no encontre 'bar' en %q", first) } // foo en col 0-2, bar en col 9-11 (columna 10 es 0-indexed 9). if len(first) < 12 { t.Fatalf("linea demasiado corta para verificar: %q", first) } // Debe haber al menos un espacio entre foo y bar. fooEnd := strings.Index(first, "foo") + 3 barStart := strings.Index(first, "bar") if barStart <= fooEnd { t.Errorf("bar empieza en %d pero foo termina en %d — sin separacion en %q", barStart, fooEnd, first) } }) t.Run("texto multilinea simple con CRLF", func(t *testing.T) { out := VTRender("linea1\r\nlinea2", 5, 40) if !strings.Contains(out, "linea1") { t.Errorf("no encontre 'linea1' en %q", out) } if !strings.Contains(out, "linea2") { t.Errorf("no encontre 'linea2' en %q", out) } lines := strings.Split(out, "\n") // linea1 y linea2 deben estar en líneas distintas. found1, found2 := -1, -1 for i, l := range lines { if strings.Contains(l, "linea1") { found1 = i } if strings.Contains(l, "linea2") { found2 = i } } if found1 == found2 { t.Errorf("linea1 y linea2 estan en la misma linea (%d) de la salida: %q", found1, out) } }) t.Run("trim de filas vacias al final de grid grande", func(t *testing.T) { // Input corto en un grid de 40 filas — no debe producir 40 lineas. out := VTRender("hola", 40, 120) count := strings.Count(out, "\n") if count >= 3 { t.Errorf("demasiadas lineas (%d) para 'hola' en grid de 40 filas: %q", count, out) } if !strings.Contains(out, "hola") { t.Errorf("no encontre 'hola' en %q", out) } }) t.Run("determinismo misma entrada misma salida", func(t *testing.T) { input := "foo\x1b[10Gbar\r\n\x1b[2;1Hbaz" out1 := VTRender(input, 10, 40) out2 := VTRender(input, 10, 40) if out1 != out2 { t.Errorf("resultados distintos:\nout1=%q\nout2=%q", out1, out2) } }) t.Run("defaults rows y cols al pasar cero", func(t *testing.T) { // Verificar que no entra en pánico con valores <= 0. out := VTRender("test", 0, 0) if !strings.Contains(out, "test") { t.Errorf("no encontre 'test' con defaults (rows=0,cols=0): %q", out) } }) }