Files
YunShu/pkg/mdprint/mdprint_test.go

309 lines
7.3 KiB
Go
Raw Normal View History

package mdprint
import (
"strings"
"testing"
)
func stripANSI(s string) string {
var b strings.Builder
i := 0
for i < len(s) {
if s[i] == '\033' && i+1 < len(s) && s[i+1] == '[' {
j := i + 2
for j < len(s) && s[j] != 'm' {
j++
}
if j < len(s) {
i = j + 1
continue
}
}
b.WriteByte(s[i])
i++
}
return b.String()
}
func TestParseInline_Text(t *testing.T) {
nodes := parseInline("hello world")
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
_, ok := nodes[0].(Text)
if !ok {
t.Fatalf("expected Text, got %T", nodes[0])
}
}
func TestParseInline_Bold(t *testing.T) {
nodes := parseInline("**bold**")
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
b, ok := nodes[0].(Bold)
if !ok {
t.Fatalf("expected Bold, got %T", nodes[0])
}
if len(b.Content) != 1 {
t.Fatalf("expected 1 child, got %d", len(b.Content))
}
txt, ok := b.Content[0].(Text)
if !ok {
t.Fatalf("expected Text in Bold, got %T", b.Content[0])
}
if txt.Text != "bold" {
t.Fatalf("expected 'bold', got '%s'", txt.Text)
}
}
func TestParseInline_Italic(t *testing.T) {
nodes := parseInline("*italic*")
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
_, ok := nodes[0].(Italic)
if !ok {
t.Fatalf("expected Italic, got %T", nodes[0])
}
}
func TestParseInline_Code(t *testing.T) {
nodes := parseInline("`code`")
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
c, ok := nodes[0].(Code)
if !ok {
t.Fatalf("expected Code, got %T", nodes[0])
}
if c.Text != "code" {
t.Fatalf("expected 'code', got '%s'", c.Text)
}
}
func TestParseInline_Link(t *testing.T) {
nodes := parseInline("[text](url)")
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d", len(nodes))
}
link, ok := nodes[0].(Link)
if !ok {
t.Fatalf("expected Link, got %T", nodes[0])
}
if link.URL != "url" {
t.Fatalf("expected URL 'url', got '%s'", link.URL)
}
if len(link.Content) != 1 {
t.Fatalf("expected 1 child in link, got %d", len(link.Content))
}
txt, ok := link.Content[0].(Text)
if !ok {
t.Fatalf("expected Text in Link, got %T", link.Content[0])
}
if txt.Text != "text" {
t.Fatalf("expected 'text', got '%s'", txt.Text)
}
}
func TestParseInline_Mixed(t *testing.T) {
nodes := parseInline("a **b** c")
if len(nodes) != 3 {
t.Fatalf("expected 3 nodes, got %d: %#v", len(nodes), nodes)
}
t1, ok := nodes[0].(Text)
if !ok || t1.Text != "a " {
t.Fatalf("expected 'a ' Text, got %#v", nodes[0])
}
_, ok = nodes[1].(Bold)
if !ok {
t.Fatalf("expected Bold at index 1, got %T", nodes[1])
}
t2, ok := nodes[2].(Text)
if !ok || t2.Text != " c" {
t.Fatalf("expected ' c' Text, got %#v", nodes[2])
}
}
func TestParseBlocks_Heading(t *testing.T) {
blocks := parseBlocks("# Title")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
h, ok := blocks[0].(Heading)
if !ok {
t.Fatalf("expected Heading, got %T", blocks[0])
}
if h.Level != 1 {
t.Fatalf("expected level 1, got %d", h.Level)
}
if stripANSI(renderNode(h)) != "\n▪ Title\n" {
t.Fatalf("got '%s'", stripANSI(renderNode(h)))
}
}
func TestParseBlocks_CodeFence(t *testing.T) {
blocks := parseBlocks("```go\npackage main\n```")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
cb, ok := blocks[0].(CodeBlock)
if !ok {
t.Fatalf("expected CodeBlock, got %T", blocks[0])
}
if cb.Lang != "go" {
t.Fatalf("expected lang 'go', got '%s'", cb.Lang)
}
body := stripANSI(renderNode(cb))
if !strings.Contains(body, "package main") {
t.Fatalf("expected 'package main' in body")
}
}
func TestParseBlocks_Blockquote(t *testing.T) {
blocks := parseBlocks("> hello\n> world")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
_, ok := blocks[0].(Blockquote)
if !ok {
t.Fatalf("expected Blockquote, got %T", blocks[0])
}
}
func TestParseBlocks_UnorderedList(t *testing.T) {
blocks := parseBlocks("- a\n- b\n- c")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
l, ok := blocks[0].(List)
if !ok {
t.Fatalf("expected List, got %T", blocks[0])
}
if len(l.Items) != 3 {
t.Fatalf("expected 3 items, got %d", len(l.Items))
}
}
func TestParseBlocks_OrderedList(t *testing.T) {
blocks := parseBlocks("1. a\n2. b")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
l, ok := blocks[0].(List)
if !ok {
t.Fatalf("expected List, got %T", blocks[0])
}
if !l.Ordered {
t.Fatalf("expected ordered list")
}
}
func TestParseBlocks_Table(t *testing.T) {
blocks := parseBlocks("| H1 | H2 |\n|---|----|\n| A | B |")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
tbl, ok := blocks[0].(Table)
if !ok {
t.Fatalf("expected Table, got %T", blocks[0])
}
if len(tbl.Headers) != 2 {
t.Fatalf("expected 2 headers, got %d", len(tbl.Headers))
}
if len(tbl.Rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(tbl.Rows))
}
}
func TestParseBlocks_ThematicBreak(t *testing.T) {
blocks := parseBlocks("---")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
_, ok := blocks[0].(ThematicBreak)
if !ok {
t.Fatalf("expected ThematicBreak, got %T", blocks[0])
}
}
func TestParseBlocks_Paragraph(t *testing.T) {
blocks := parseBlocks("hello\nworld")
if len(blocks) != 1 {
t.Fatalf("expected 1 block, got %d", len(blocks))
}
_, ok := blocks[0].(Paragraph)
if !ok {
t.Fatalf("expected Paragraph, got %T", blocks[0])
}
}
func TestParseBlocks_MultiBlock(t *testing.T) {
content := "# Title\n\nhello **world**\n\n> quote"
blocks := parseBlocks(content)
if len(blocks) != 3 {
t.Fatalf("expected 3 blocks, got %d", len(blocks))
}
}
func TestRender_HeadingLevels(t *testing.T) {
for level := 1; level <= 6; level++ {
prefix := strings.Repeat("#", level)
blocks := parseBlocks(prefix + " test")
if len(blocks) != 1 {
t.Fatalf("heading level %d: expected 1 block", level)
}
h, ok := blocks[0].(Heading)
if !ok {
t.Fatalf("heading level %d: expected Heading", level)
}
if h.Level != level {
t.Fatalf("heading level %d: expected level=%d", level, level)
}
result := stripANSI(renderNode(h))
var expected string
switch level {
case 1:
expected = "\n▪ test\n"
case 2, 3:
expected = "\n▪ test"
case 4, 5:
expected = "\n▫ test"
case 6:
expected = "\ntest"
}
if result != expected {
t.Fatalf("heading level %d: got '%q', expected '%q'", level, result, expected)
}
}
}
func TestRender_BoldItalic(t *testing.T) {
nodes := parseInline("**bold** and *italic*")
r := stripANSI(renderInline(nodes))
if r != "bold and italic" {
t.Fatalf("got '%s'", r)
}
}
func TestRender_Link(t *testing.T) {
nodes := parseInline("[click](https://example.com)")
r := stripANSI(renderInline(nodes))
if !strings.Contains(r, "click") || !strings.Contains(r, "https://example.com") {
t.Fatalf("got '%s'", r)
}
}
func TestRender_FullDocument(t *testing.T) {
content := "# 天气报告\n\n今天**晴**,温度 *适中*。\n\n> 温馨提示:出门带伞\n\n- 温度25°C\n- 湿度40%\n\n代码`fmt.Println(\"hello\")`\n\n| 项目 | 值 |\n|------|----|\n| AQI | 42 |\n"
blocks := parseBlocks(content)
if len(blocks) == 0 {
t.Fatal("expected blocks")
}
_, ok := blocks[0].(Heading)
if !ok {
t.Fatal("first block should be Heading")
}
}