返回 DeepSeek-Reasonix
md_test.go
根目录 / internal / cli / md_test.go
1 package cli
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 // TestRenderEmpty covers the contract that empty / whitespace-only input
9 // returns "" — callers rely on this to skip a redraw when there's nothing
10 // substantive to show.
11 func TestRenderEmpty(t *testing.T) {
12 r := newMarkdownRenderer(80)
13 for _, in := range []string{"", " ", "\n", "\t\n \n"} {
14 if got := r.Render(in); got != "" {
15 t.Errorf("Render(%q) = %q, want empty", in, got)
16 }
17 }
18 }
19
20 // TestRenderConstructsRound-trip checks each major construct emits something
21 // styled while preserving the underlying text. We don't assert exact ANSI
22 // sequences (palette could shift) — only that key visible text survives and
23 // that we don't degrade to literal markdown.
24 func TestRenderConstructs(t *testing.T) {
25 r := newMarkdownRenderer(80)
26 cases := []struct {
27 name string
28 in string
29 contains []string
30 notRaw []string // substrings that must NOT appear (raw markdown leaking through)
31 }{
32 {
33 name: "heading",
34 in: "# Hello\n",
35 contains: []string{"Hello"},
36 notRaw: []string{"# Hello", "## "},
37 },
38 {
39 name: "heading h2 drops prefix",
40 in: "## Section\n",
41 contains: []string{"Section"},
42 notRaw: []string{"## ", "###"},
43 },
44 {
45 name: "bold",
46 in: "this is **important** text",
47 contains: []string{"important", "this is", "text"},
48 },
49 {
50 name: "italic",
51 in: "see *here* for details",
52 contains: []string{"here", "see", "for details"},
53 },
54 {
55 name: "code span",
56 in: "use `os.Setenv` to set",
57 contains: []string{"os.Setenv", "use", "to set"},
58 },
59 {
60 name: "unordered list",
61 in: "- one\n- two\n- three\n",
62 contains: []string{"one", "two", "three", "•"},
63 },
64 {
65 name: "ordered list",
66 in: "1. first\n2. second\n",
67 contains: []string{"first", "second", "1.", "2."},
68 },
69 {
70 name: "fenced code",
71 in: "```go\nfunc main() {}\n```\n",
72 contains: []string{"func main()"},
73 },
74 {
75 name: "thematic break",
76 in: "above\n\n---\n\nbelow",
77 contains: []string{"above", "below", "─"},
78 },
79 {
80 name: "gfm table",
81 in: "| name | size |\n|------|------|\n| a | 12 |\n| bb | 345 |\n",
82 contains: []string{"name", "size", "a", "12", "bb", "345", "│"},
83 notRaw: []string{"|------|"}, // raw separator must be transformed
84 },
85 }
86 for _, tc := range cases {
87 t.Run(tc.name, func(t *testing.T) {
88 out := r.Render(tc.in)
89 for _, want := range tc.contains {
90 if !strings.Contains(out, want) {
91 t.Errorf("Render(%q) missing %q\n--- output ---\n%s", tc.in, want, out)
92 }
93 }
94 for _, leak := range tc.notRaw {
95 if strings.Contains(out, leak) {
96 t.Errorf("Render(%q) leaked raw markdown %q", tc.in, leak)
97 }
98 }
99 })
100 }
101 }
102
103 // TestWrapAnsiCJK proves the wrap counter treats CJK as 2 cols, so a line of
104 // Chinese characters wraps at half the column count.
105 func TestWrapAnsiCJK(t *testing.T) {
106 // Width 10 = room for 5 Chinese characters per row.
107 in := strings.Repeat("中", 8)
108 out := wrapAnsi(in, 10)
109 lines := strings.Split(out, "\n")
110 if len(lines) < 2 {
111 t.Fatalf("expected wrap, got 1 line: %q", out)
112 }
113 if visibleWidth(lines[0]) > 10 {
114 t.Errorf("first line exceeds width: %d > 10", visibleWidth(lines[0]))
115 }
116 }
117
117 lines GO