// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package doc
import (
"reflect"
"strings"
"testing"
)
func TestApplyFetchIMMarkdown(t *testing.T) {
t.Parallel()
tests := []struct {
name string
data map[string]interface{}
docInput string
want map[string]interface{}
}{
{
name: "missing document leaves data unchanged",
data: map[string]interface{}{
"content": `
Roadmap `,
},
docInput: "https://tenant.example.com/docx/doc_token",
want: map[string]interface{}{
"content": `Roadmap `,
},
},
{
name: "non string content leaves data unchanged",
data: map[string]interface{}{
"document": map[string]interface{}{
"content": 123,
},
},
docInput: "https://tenant.example.com/docx/doc_token",
want: map[string]interface{}{
"document": map[string]interface{}{
"content": 123,
},
},
},
{
name: "converts content with tenant base url",
data: map[string]interface{}{
"document": map[string]interface{}{
"content": `Roadmap ` + "\n" + ` `,
},
},
docInput: "https://tenant.example.com/docx/doc_token",
want: map[string]interface{}{
"document": map[string]interface{}{
"content": "# Roadmap\n[sheet S1](https://tenant.example.com/sheets/sht_token)",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
applyFetchIMMarkdown(tt.data, tt.docInput)
if !reflect.DeepEqual(tt.data, tt.want) {
t.Fatalf("data = %#v, want %#v", tt.data, tt.want)
}
})
}
}
func TestConvertToIMMarkdownTitle(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "plain title",
input: `Roadmap `,
want: "# Roadmap",
},
{
name: "trim title whitespace",
input: "\n Roadmap \n ",
want: "# Roadmap",
},
{
name: "convert title inner markup",
input: `Bold Title `,
want: "# **Bold** Title",
},
{
name: "empty title",
input: ` `,
want: "",
},
{
name: "title followed by text",
input: `Roadmap tail`,
want: "# Roadmaptail",
},
{
name: "uppercase title is handled case-insensitively",
input: `Roadmap `,
want: "# Roadmap",
},
{
name: "missing closing title is preserved",
input: `beforeRoadmap`,
want: `beforeRoadmap`,
},
})
}
func TestConvertToIMMarkdownCallout(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "emoji and body",
input: `Read **this**. `,
want: "---\n๐ก Read **this**.\n---",
},
{
name: "body without emoji",
input: `Plain body `,
want: "---\nPlain body\n---",
},
{
name: "emoji only",
input: ` `,
want: "---\nโ
\n---",
},
{
name: "empty callout",
input: ` `,
want: "---\n---",
},
{
name: "nested callout",
input: `Outer Inner `,
want: "---\nโ
Outer ---\n๐ก Inner\n---\n---",
},
{
name: "callout contains registered tags",
input: ` `,
want: "---\n๐ [Spec](https://example.com)\n---",
},
{
name: "callout contains grid and cite",
input: ` `,
want: "---\n๐ฃ Alice \n[Spec](https://example.com)\n---",
},
{
name: "same-name nested callout with trailing text",
input: `ab c d`,
want: "---\n1 a---\n2 b\n---c\n---d",
},
{
name: "missing closing callout is preserved",
input: `beforebody`,
want: `beforebody`,
},
})
}
func TestConvertToIMMarkdownBlockquote(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "single paragraph",
input: `quote link
`,
want: "> quote [link](https://example.com)",
},
{
name: "multiple paragraphs keep line breaks",
input: `first
second
`,
want: "> first\n> **second**",
},
{
name: "nested blockquote keeps nested markers",
input: `outer
inner
`,
want: "> outer\n> > inner",
},
{
name: "blank line keeps quote marker",
input: "first\n\nsecond ",
want: "> first\n>\n> second",
},
{
name: "empty blockquote",
input: ` `,
want: "",
},
{
name: "plain adjacent paragraphs outside blockquote stay compact",
input: `first
second
`,
want: "firstsecond",
},
})
}
func TestConvertToIMMarkdownParagraphHeadingAndListItemEdges(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "empty heading",
input: ` `,
want: "",
},
{
name: "empty paragraph",
input: `
`,
want: "",
},
{
name: "top level list item uses seq",
input: "first\nsecond ",
want: "7. first\n second\n",
},
{
name: "top level empty list item",
input: ` `,
want: "",
},
{
name: "unordered list skips non item text and empty items",
input: ``,
want: "- first\n- second",
},
{
name: "unclosed list item stops list scan",
input: ``,
want: "- first",
},
})
}
func TestConvertToIMMarkdownGridAndColumn(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "two columns",
input: `Left Right `,
want: "Left\nRight",
},
{
name: "column converts nested registered tags",
input: ` `,
want: "[Spec](https://example.com)\n",
},
{
name: "empty column",
input: ` `,
want: "",
},
{
name: "nested grid",
input: `A B C `,
want: "A\nB\nC",
},
{
name: "grid inside callout",
input: `A B `,
want: "---\n๐ A\nB\n---",
},
{
name: "adjacent grids do not merge",
input: `A B `,
want: "AB",
},
{
name: "column with nested callout keeps recursive output",
input: `Tip `,
want: "---\n๐ก Tip\n---\n",
},
{
name: "missing closing grid is preserved",
input: `A `,
want: `A `,
},
})
}
func TestConvertToIMMarkdownTable(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "basic table",
input: ``,
want: "| A | B |\n| - | - |\n| 1 | 2 |",
},
{
name: "table strips attrs and preserves cell line break",
input: ``,
want: "| A | B |\n| - | - |\n| 1 | **two** lines |",
},
{
name: "table escapes pipe",
input: ``,
want: "| A\\|B |\n| - |\n| x\\|y |",
},
{
name: "table pads ragged rows",
input: ``,
want: "| A | B |\n| - | - |\n| 1 | |",
},
{
name: "table converts nested cite",
input: ``,
want: "| User |\n| - |\n| Alice |",
},
{
name: "table converts nested bookmark and sheet",
input: ``,
want: "| Link | Sheet |\n| - | - |\n| [Spec](https://example.com) | [sheet S1](https://larkoffice.com/sheets/sht_1) |",
},
{
name: "table strips nested unknown html but preserves text",
input: ``,
want: "| A |\n| - |\n| red under |",
},
{
name: "table normalizes markdown hard breaks",
input: "",
want: "| A |\n| - |\n| line1 line2 |",
},
{
name: "table cell keeps nested table whole",
input: ``,
want: "| Outer |\n| - |\n| before \\| Inner \\| \\| - \\| \\| x \\| after |",
},
{
name: "table with only data row treats first row as header",
input: ``,
want: "| A | B |\n| - | - |",
},
{
name: "table without rows falls back to inline code",
input: ``,
want: "``",
},
{
name: "table row without cells falls back to inline code",
input: ``,
want: "``",
},
{
name: "table self closing row falls back to inline code",
input: ``,
want: "``",
},
{
name: "table empty cell stays empty",
input: ``,
want: "| |\n| - |",
},
{
name: "missing closing table is preserved",
input: `beforeA `,
want: `beforeA `,
},
})
}
func TestIMMarkdownElementExtractionEdges(t *testing.T) {
t.Parallel()
bodies := extractIMMarkdownElementBodies(` x open`, imMarkdownRowTagRE)
if want := []string{"", "x "}; !reflect.DeepEqual(bodies, want) {
t.Fatalf("extractIMMarkdownElementBodies() = %#v, want %#v", bodies, want)
}
if _, _, ok := findIMMarkdownElementClosingTag(` x`, len(" "), imMarkdownRowTagRE); ok {
t.Fatal("findIMMarkdownElementClosingTag() found closing tag, want false")
}
start, end, ok := findIMMarkdownListItemClosingTag(`outer tail`, len(""))
if !ok {
t.Fatal("findIMMarkdownListItemClosingTag() did not find closing tag")
}
if got, want := ` outer tail`[start:end], ""; got != want {
t.Fatalf("closing tag = %q, want %q", got, want)
}
if _, _, ok := findIMMarkdownListItemClosingTag(`open`, len(" ")); ok {
t.Fatal("findIMMarkdownListItemClosingTag() found closing tag, want false")
}
start, end, ok = findIMMarkdownListItemClosingTag(` outer inner tail`, len(""))
if !ok {
t.Fatal("findIMMarkdownListItemClosingTag() did not find nested closing tag")
}
if got, want := ` outer inner tail`[start:end], ""; got != want {
t.Fatalf("nested closing tag = %q, want %q", got, want)
}
if got := convertIMMarkdownListItems("plain text", false, imMarkdownContext{}); got != "" {
t.Fatalf("convertIMMarkdownListItems() = %q, want empty", got)
}
}
func TestNormalizeIMMarkdownTableCellStripsUnknownTags(t *testing.T) {
t.Parallel()
got := normalizeIMMarkdownTableCell(`red `)
if want := "red"; got != want {
t.Fatalf("normalizeIMMarkdownTableCell() = %q, want %q", got, want)
}
}
func TestConvertToIMMarkdownDiscardTags(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "figure discarded",
input: `beforehidden after`,
want: "beforeafter",
},
{
name: "figure with source discarded",
input: ` `,
want: "",
},
{
name: "self-closing source discarded",
input: `a b`,
want: "ab",
},
{
name: "source name becomes inline code",
input: "ab",
want: "a``report`v1`.pdf``b",
},
{
name: "button discarded",
input: `aClick b`,
want: "ab",
},
{
name: "time discarded",
input: `a b`,
want: "ab",
},
{
name: "colgroup discarded",
input: `a b`,
want: "ab",
},
{
name: "col discarded",
input: `a b`,
want: "ab",
},
{
name: "self-closing button discarded",
input: `a b`,
want: "ab",
},
{
name: "missing closing discard tag is preserved",
input: `ahidden`,
want: `ahidden`,
},
})
}
func TestConvertToIMMarkdownWhiteboard(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "paired whiteboard",
input: ` `,
want: "` `",
},
{
name: "self-closing whiteboard",
input: ` `,
want: "``",
},
{
name: "whiteboard with backticks",
input: " ",
want: "`` ``",
},
{
name: "whiteboard preserves inner text as opaque",
input: `not exported `,
want: "`not exported `",
},
{
name: "missing closing whiteboard is preserved",
input: ``,
want: ``,
},
})
}
func TestConvertToIMMarkdownSheet(t *testing.T) {
t.Parallel()
assertIMMarkdownCasesWithContext(t, imMarkdownContext{baseURL: "https://bytedance.larkoffice.com"}, []imMarkdownCase{
{
name: "sheet with sheet id",
input: ` `,
want: "[sheet S1](https://bytedance.larkoffice.com/sheets/sht_token)",
},
{
name: "sheet without sheet id",
input: ` `,
want: "[sheet](https://bytedance.larkoffice.com/sheets/sht_token)",
},
{
name: "sheet without token falls back to inline code",
input: ` `,
want: "` `",
},
{
name: "self-closing sheet",
input: ` `,
want: "[sheet S1](https://bytedance.larkoffice.com/sheets/sht_token)",
},
{
name: "sheet token is trimmed",
input: ` `,
want: "[sheet S1](https://bytedance.larkoffice.com/sheets/sht_token)",
},
{
name: "sheet inside text",
input: `before after`,
want: "before [sheet](https://bytedance.larkoffice.com/sheets/sht_token) after",
},
})
}
func TestConvertToIMMarkdownBookmark(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "name and href",
input: ` `,
want: "[Example](https://example.com)",
},
{
name: "title fallback",
input: ` `,
want: "[Example](https://example.com)",
},
{
name: "inner text fallback",
input: `Example `,
want: "[Example](https://example.com)",
},
{
name: "missing href returns label",
input: ` `,
want: "Example",
},
{
name: "escaped link label",
input: ` `,
want: "[A \\[B\\]](https://example.com)",
},
{
name: "href is percent encoded",
input: ` `,
want: "[Spec](https://example.com/wiki/A%20B%20%28draft%29?q=x%20y#frag%281%29)",
},
{
name: "href keeps existing percent escapes",
input: ` `,
want: "[Spec](https://example.com/wiki/A%20B)",
},
{
name: "href escapes invalid percent and unicode",
input: ` `,
want: "[Spec](https://example.com/wiki/%E7%A0%94%E5%8F%91%25zz?x=1%25)",
},
{
name: "href escapes markdown delimiter bytes",
input: " ",
want: "[Spec](https://example.com/a%3Cb%3E%7Cc%60d)",
},
{
name: "inner registered tag fallback",
input: ` `,
want: "[Alice](https://example.com)",
},
{
name: "href fallback as label",
input: ` `,
want: "[https://example.com](https://example.com)",
},
{
name: "self-closing bookmark without href",
input: ` `,
want: "Example",
},
})
}
func TestConvertToIMMarkdownInlineEdges(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "empty strong emphasis and delete",
input: ` `,
want: "",
},
{
name: "anchor without href returns text",
input: `plain text `,
want: "plain **text**",
},
{
name: "anchor without text falls back to href",
input: ` `,
want: "[https://example.com/a b](https://example.com/a%20b)",
},
{
name: "latex escapes dollars",
input: `price=$5 `,
want: "$price=\\$5$",
},
{
name: "empty latex",
input: ` `,
want: "",
},
{
name: "image missing href",
input: ` `,
want: "",
},
{
name: "image uses src and title fallback",
input: ` `,
want: "![A \\[img\\]](https://example.com/i%201.png)",
},
{
name: "plain fenced code",
input: `plain `,
want: "```\nplain\n```",
},
{
name: "code inline trims nested markup",
input: `x `,
want: "`x`",
},
})
}
func TestConvertToIMMarkdownCiteUser(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "user id and name",
input: ` `,
want: `Alice `,
},
{
name: "open id fallback",
input: ` `,
want: `Bob `,
},
{
name: "name falls back to user id",
input: ` `,
want: `ou_abc `,
},
{
name: "missing user id returns name",
input: ` `,
want: "Alice",
},
{
name: "escape at XML",
input: ` `,
want: `A&B `,
},
{
name: "inner text fallback when attrs missing name",
input: `Alice `,
want: `Alice `,
},
{
name: "self-closing user cite",
input: ` `,
want: `Alice `,
},
})
}
func TestConvertToIMMarkdownCiteDoc(t *testing.T) {
t.Parallel()
assertIMMarkdownCasesWithContext(t, imMarkdownContext{baseURL: "https://bytedance.larkoffice.com"}, []imMarkdownCase{
{
name: "doc id to link",
input: ` `,
want: "[Spec](https://bytedance.larkoffice.com/docx/doc_token)",
},
{
name: "href wins",
input: ` `,
want: "[Spec](https://example.com/doc%20%28draft%29)",
},
{
name: "default title and file type",
input: ` `,
want: "[document](https://bytedance.larkoffice.com/docx/doc_token)",
},
{
name: "missing doc id falls back to inline code",
input: ` `,
want: "` `",
},
{
name: "wiki file type link",
input: ` `,
want: "[Wiki](https://bytedance.larkoffice.com/wiki/wiki_token)",
},
{
name: "doc title is escaped",
input: ` `,
want: "[A \\[B\\]](https://bytedance.larkoffice.com/docx/doc_token)",
},
})
}
func TestConvertToIMMarkdownCiteCitation(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "inner anchor",
input: `Ref `,
want: "[Ref](https://example.com/ref)",
},
{
name: "href attr",
input: ` `,
want: "[Ref](https://example.com/ref)",
},
{
name: "plain inner fallback",
input: `Plain Ref `,
want: "Plain Ref",
},
{
name: "inner anchor text strips markup",
input: `Ref `,
want: "[Ref](https://example.com/ref)",
},
{
name: "single quoted inner anchor falls back to href text",
input: ` `,
want: "[https://example.com/ref](https://example.com/ref)",
},
{
name: "href attr falls back to href label",
input: ` `,
want: "[https://example.com/ref](https://example.com/ref)",
},
})
}
func TestEscapeMarkdownLinkDestinationInvalidUTF8(t *testing.T) {
t.Parallel()
got := escapeMarkdownLinkDestination(string([]byte{'a', 0xff, 'b'}))
if want := "a%FFb"; got != want {
t.Fatalf("escapeMarkdownLinkDestination() = %q, want %q", got, want)
}
}
func TestConvertToIMMarkdownCiteUnknown(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "unknown paired cite",
input: `x `,
want: "`x `",
},
{
name: "unknown self-closing cite",
input: ` `,
want: "``",
},
})
}
func TestConvertToIMMarkdownScannerBoundaries(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "unknown tag preserved with known child untouched",
input: ` `,
want: `[Spec](https://example.com) `,
},
{
name: "registered tag attributes single quotes",
input: ` `,
want: "[Spec](https://example.com)",
},
{
name: "registered tag name with leading text",
input: `alphaBeta gamma`,
want: "alpha# Betagamma",
},
{
name: "xml comment is preserved",
input: `aT `,
want: "a# T",
},
{
name: "br is preserved",
input: `a b`,
want: "a b",
},
{
name: "malformed attribute still allows handler",
input: `Inner `,
want: "[Inner](https://example.com)",
},
})
}
func TestConvertToIMMarkdownCompositeNesting(t *testing.T) {
t.Parallel()
assertIMMarkdownCasesWithContext(t, imMarkdownContext{baseURL: "https://tenant.example.com"}, []imMarkdownCase{
{
name: "callout grid table and resources",
input: ` `,
want: "---\n๐ | Owner | Doc |\n| - | - |\n| Alice | [Spec](https://tenant.example.com/docx/doc_1) |\n[sheet S1](https://tenant.example.com/sheets/sht_1)\n---",
},
{
name: "grid inside table cell",
input: ``,
want: "| Outer |\n| - |\n| A B |",
},
{
name: "table inside table cell",
input: ``,
want: "| Outer | Tail |\n| - | - |\n| \\| Inner \\| \\| - \\| \\| x \\| | done |",
},
{
name: "bookmark wraps callout fallback text",
input: `Tip `,
want: "[๐ก Tip](https://example.com)",
},
})
}
func TestConvertToIMMarkdownUnclosedFragments(t *testing.T) {
t.Parallel()
assertIMMarkdownCases(t, []imMarkdownCase{
{
name: "unclosed title preserves nested registered tag",
input: `before `,
want: `before `,
},
{
name: "unclosed callout preserves nested registered tag",
input: `before `,
want: `before `,
},
{
name: "unclosed grid preserves closed child",
input: `beforeA `,
want: `beforeA `,
},
{
name: "unclosed column preserves nested registered tag",
input: `before `,
want: `before `,
},
{
name: "unclosed table preserves nested cite",
input: `before `,
want: `before `,
},
{
name: "unclosed figure preserves nested source",
input: `before `,
want: `before `,
},
{
name: "unclosed whiteboard preserves nested registered tag",
input: `before `,
want: `before `,
},
{
name: "unclosed sheet preserves nested registered tag",
input: `before `,
want: `before `,
},
{
name: "unclosed bookmark preserves nested cite",
input: `before `,
want: `before `,
},
{
name: "unclosed cite preserves inner anchor",
input: `beforeRef `,
want: `beforeRef `,
},
})
}
func TestConvertToIMMarkdownDeepRegisteredContainers(t *testing.T) {
t.Parallel()
deepGrid := "leaf"
for i := 0; i < 32; i++ {
deepGrid = "" + deepGrid + " "
}
if got := convertToIMMarkdown(deepGrid, imMarkdownContext{}); got != "leaf" {
t.Fatalf("deep grid conversion = %q, want %q", got, "leaf")
}
deepCallout := "leaf"
for i := 0; i < 16; i++ {
deepCallout = `` + deepCallout + ` `
}
got := convertToIMMarkdown(deepCallout, imMarkdownContext{})
if !strings.Contains(got, "leaf") {
t.Fatalf("deep callout conversion missing leaf:\n%s", got)
}
if count := strings.Count(got, "๐ก"); count != 16 {
t.Fatalf("deep callout emoji count = %d, want 16\n%s", count, got)
}
}
func TestConvertToIMMarkdownDocumentExpectedTagsAndEscaping(t *testing.T) {
t.Parallel()
imCtx := imMarkdownContext{baseURL: "https://bytedance.larkoffice.com"}
input := strings.Join([]string{
`Roadmap Q1 `,
`Deep Heading `,
`plain next Bold Italic Gone Under Plain A [B]
`,
`quote Card
`,
``,
`one three `,
`fmt.Println("hi")` + "\n```" + ` `,
`` + "`edge`" + ` E=mc^2
`,
` `,
` `,
` `,
}, "\n")
want := strings.Join([]string{
`# Roadmap Q1`,
`###### Deep Heading`,
`plain next **Bold** *Italic* ~~Gone~~ Under Plain [A \[B\]](https://example.com/a%28b%29)`,
`> quote [Card](https://example.com/card)`,
`- first`,
`- **second**`,
`1. one`,
`3. three`,
"````Go\nfmt.Println(\"hi\")\n```\n````",
"`` `edge` `` $E=mc^2$ --- ![A \\[img\\]](https://example.com/i%281%29.png)",
"``report`v1`.pdf``",
"`ไปปๅก``็พค่ๅก็`",
"`ๅค็ปด่กจๆ ผ``ๅค็ปด่กจๆ ผ``OKR`",
}, "\n")
if got := convertToIMMarkdown(input, imCtx); got != want {
t.Fatalf("convertToIMMarkdown() = %q, want %q", got, want)
}
}
func TestConvertToIMMarkdownMixedDocumentSmoke(t *testing.T) {
t.Parallel()
imCtx := imMarkdownContext{baseURL: "https://bytedance.larkoffice.com"}
input := strings.Join([]string{
`Roadmap `,
`### Left Right `,
``,
` `,
` `,
`Ref `,
` `,
` `,
}, "\n")
got := convertToIMMarkdown(input, imCtx)
for _, want := range []string{
"# Roadmap",
"### Left",
"Right",
"| A | B |\n| - | - |\n| 1 | **two** lines |",
`Alice `,
"[Spec](https://bytedance.larkoffice.com/docx/doc_token)",
"[Ref](https://example.com/ref)",
"[sheet S1](https://bytedance.larkoffice.com/sheets/sht_token)",
} {
if !strings.Contains(got, want) {
t.Fatalf("converted content missing %q:\n%s", want, got)
}
}
for _, dropped := range []string{" first\n>\n> second",
},
{
name: "empty latex",
got: handleIMMarkdownLatex("", " ", nil, ctx),
want: "",
},
{
name: "image without URL",
got: handleIMMarkdownImage("", "", map[string]string{"alt": "A"}, ctx),
want: "",
},
{
name: "empty strong",
got: handleIMMarkdownStrong("", " ", nil, ctx),
want: "",
},
{
name: "empty emphasis",
got: handleIMMarkdownEmphasis("", " ", nil, ctx),
want: "",
},
{
name: "empty delete",
got: handleIMMarkdownDelete("", " ", nil, ctx),
want: "",
},
{
name: "anchor without href",
got: handleIMMarkdownAnchor("", "plain ", nil, ctx),
want: "**plain**",
},
{
name: "table skips rows without cells",
got: handleIMMarkdownTable("", " ", nil, ctx),
want: "``",
},
{
name: "empty normalized table cell",
got: normalizeIMMarkdownTableCell(" "),
want: "",
},
{
name: "plain fenced code uses minimum fence",
got: imMarkdownFencedCode("plain", ""),
want: "```\nplain\n```",
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if tt.got != tt.want {
t.Fatalf("got %q, want %q", tt.got, tt.want)
}
})
}
}
func TestIMMarkdownExtractionAndListBreakBranches(t *testing.T) {
t.Parallel()
rowBodies := extractIMMarkdownElementBodies(`open`, imMarkdownRowTagRE)
if want := []string{""}; !reflect.DeepEqual(rowBodies, want) {
t.Fatalf("extractIMMarkdownElementBodies() = %#v, want %#v", rowBodies, want)
}
if _, _, ok := findIMMarkdownElementClosingTag(` open `, len(""), imMarkdownRowTagRE); ok {
t.Fatal("findIMMarkdownElementClosingTag() found closing tag, want false")
}
if got := convertIMMarkdownListItems("", false, imMarkdownContext{}); got != "" {
t.Fatalf("empty list conversion = %q, want empty", got)
}
if got := convertIMMarkdownListItems("open", false, imMarkdownContext{}); got != "" {
t.Fatalf("unclosed list conversion = %q, want empty", got)
}
if _, _, ok := findIMMarkdownListItemClosingTag(` outer inner `, len("")); ok {
t.Fatal("findIMMarkdownListItemClosingTag() found closing tag for unbalanced nested item")
}
}
func TestIMMarkdownLinkAndEncodingFallbackBranches(t *testing.T) {
t.Parallel()
text, href, ok := extractIMMarkdownInnerLink(` `)
if !ok {
t.Fatal("extractIMMarkdownInnerLink() ok = false, want true")
}
if text != "https://example.com/ref" || href != "https://example.com/ref" {
t.Fatalf("inner link = (%q, %q), want href fallback", text, href)
}
if got := escapeMarkdownLinkDestination("a%zz%"); got != "a%25zz%25" {
t.Fatalf("escaped invalid percent = %q, want %q", got, "a%25zz%25")
}
if got := escapeMarkdownLinkDestination("็ ๅ"); got != "%E7%A0%94%E5%8F%91" {
t.Fatalf("escaped unicode = %q, want encoded UTF-8 bytes", got)
}
if got := escapeMarkdownLinkDestination(string([]byte{'a', 0xff, 'b'})); got != "a%FFb" {
t.Fatalf("escaped invalid UTF-8 = %q, want %q", got, "a%FFb")
}
}
type imMarkdownCase struct {
name string
input string
want string
}
func assertIMMarkdownCases(t *testing.T, cases []imMarkdownCase) {
t.Helper()
assertIMMarkdownCasesWithContext(t, imMarkdownContext{baseURL: "https://larkoffice.com"}, cases)
}
func assertIMMarkdownCasesWithContext(t *testing.T, imCtx imMarkdownContext, cases []imMarkdownCase) {
t.Helper()
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := convertToIMMarkdown(tt.input, imCtx); got != tt.want {
t.Fatalf("convertToIMMarkdown() = %q, want %q", got, tt.want)
}
})
}
}