Download
95 lines 3.1 KiBGoModified 2026-10-26 raw
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
package tui

import (
	"strings"
	"testing"

	"github.com/charmbracelet/x/ansi"
)

func TestMarkdownInline(t *testing.T) {
	for in, want := range map[string]string{
		"**bold** and *it*":     "bold and it",
		"snake_case_name":       "snake_case_name",
		"2 * 3 * 4":             "2 * 3 * 4",
		"`a*b*c` code":          "a*b*c code",
		`\*not\*`:               "*not*",
		"[docs](https://x.y/z)": "docs (https://x.y/z)",
		"~~gone~~ **unclosed":   "gone **unclosed",
		"## Heading":            "Heading",
		"- item":                "• item",
		"  3. third":            "  3. third",
		"> quoted":              "│ quoted",
	} {
		if got := ansi.Strip(markdown(in, 80)); got != want {
			t.Errorf("markdown(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestMarkdownTable(t *testing.T) {
	in := "| Name | N |\n|:-----|--:|\n| a `x|y` | 1 |\n| long name | 22 |"
	want := strings.Join([]string{
		"┌───────────┬────┐",
		"│ Name      │  N │",
		"├───────────┼────┤",
		"│ a x|y     │  1 │",
		"│ long name │ 22 │",
		"└───────────┴────┘",
	}, "\n")
	if got := ansi.Strip(markdown(in, 80)); got != want {
		t.Errorf("table:\n%s\nwant:\n%s", got, want)
	}
	// Too wide for the screen: cells wrap, and no line is wider than it.
	wide := "| a | b |\n|---|---|\n| " + strings.Repeat("word ", 20) + "| " + strings.Repeat("more ", 20) + "|"
	for _, l := range strings.Split(markdown(wide, 40), "\n") {
		if w := ansi.StringWidth(l); w > 40 {
			t.Errorf("line %q is %d wide", ansi.Strip(l), w)
		}
	}
}

// Every line stands alone: it ends with a reset, so a style cannot run
// into the next one or the interface after it.
func TestMarkdownLinesReset(t *testing.T) {
	out := markdown("**"+strings.Repeat("bold ", 30)+"**\n```\ncode\n```", 40)
	for _, l := range strings.Split(out, "\n") {
		if strings.Contains(l, "\x1b") && !strings.HasSuffix(l, sgrReset) {
			t.Errorf("line %q does not end with a reset", l)
		}
	}
}

// A row with more cells than the header shows them all.
func TestTableExtraCells(t *testing.T) {
	out := ansi.Strip(markdown("| a | b |\n|---|---|\n| x | y | HIDDEN |\n", 80))
	if !strings.Contains(out, "HIDDEN") {
		t.Errorf("lost a cell:\n%s", out)
	}
}

// Rendering keeps every word of the text: Markdown syntax goes, but
// nothing the model wrote may vanish from the screen.
func FuzzMarkdownKeepsWords(f *testing.F) {
	for _, s := range []string{
		"| a | b |\n|---|---|\n| x | y | z |",
		"# Title\n\n*em* **strong** `code` [text](https://u.example/path) ~~gone~~",
		"```go extra words\nfunc main() {}\n```",
		"> quote\n- item\n1. first\n\n---\n",
		"a\\*b\\_c <html> &amp;",
	} {
		f.Add(s)
	}
	f.Fuzz(func(t *testing.T, s string) {
		s = Sanitize(s)
		out := ansi.Strip(markdown(s, 10000))
		flat := strings.Join(strings.Fields(out), "")
		for _, w := range strings.FieldsFunc(s, func(r rune) bool {
			return !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9')
		}) {
			if !strings.Contains(flat, w) {
				t.Fatalf("markdown(%q) lost %q:\n%s", s, w, out)
			}
		}
	})
}