package converter
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// === Markdown to Confluence XML tests ===
func TestMarkdownToConfluence_Heading(t *testing.T) {
result, err := MarkdownToConfluence([]byte("# Hello World"))
require.NoError(t, err)
assert.Contains(t, result, "
Hello World
")
}
func TestMarkdownToConfluence_AllHeadingLevels(t *testing.T) {
for i, tag := range []string{"h1", "h2", "h3", "h4", "h5", "h6"} {
prefix := strings.Repeat("#", i+1)
result, err := MarkdownToConfluence([]byte(prefix + " Heading"))
require.NoError(t, err)
assert.Contains(t, result, "<"+tag+">Heading"+tag+">", "level %d", i+1)
}
}
func TestMarkdownToConfluence_Bold(t *testing.T) {
result, err := MarkdownToConfluence([]byte("**bold text**"))
require.NoError(t, err)
assert.Contains(t, result, "bold text")
}
func TestMarkdownToConfluence_Italic(t *testing.T) {
result, err := MarkdownToConfluence([]byte("*italic text*"))
require.NoError(t, err)
assert.Contains(t, result, "italic text")
}
func TestMarkdownToConfluence_Strikethrough(t *testing.T) {
result, err := MarkdownToConfluence([]byte("~~deleted~~"))
require.NoError(t, err)
assert.Contains(t, result, "deleted")
}
func TestMarkdownToConfluence_InlineCode(t *testing.T) {
result, err := MarkdownToConfluence([]byte("`code`"))
require.NoError(t, err)
assert.Contains(t, result, "code")
}
func TestMarkdownToConfluence_CodeBlock(t *testing.T) {
input := "```go\nfmt.Println(\"hello\")\n```"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, `ac:name="code"`)
assert.Contains(t, result, `ac:name="language">go`)
assert.Contains(t, result, ``)
}
func TestMarkdownToConfluence_CodeBlockNoLanguage(t *testing.T) {
input := "```\nsome code\n```"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, `ac:name="code"`)
assert.NotContains(t, result, `ac:name="language"`)
}
func TestMarkdownToConfluence_Table(t *testing.T) {
input := "| A | B |\n|---|---|\n| 1 | 2 |"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, "")
assert.Contains(t, result, "A | ")
assert.Contains(t, result, "1 | ")
}
func TestMarkdownToConfluence_Blockquote(t *testing.T) {
result, err := MarkdownToConfluence([]byte("> Important note"))
require.NoError(t, err)
assert.Contains(t, result, `ac:name="info"`)
assert.Contains(t, result, "Important note")
}
func TestMarkdownToConfluence_Image(t *testing.T) {
result, err := MarkdownToConfluence([]byte(""))
require.NoError(t, err)
assert.Contains(t, result, "Click here`)
}
func TestMarkdownToConfluence_UnorderedList(t *testing.T) {
input := "- One\n- Two\n- Three"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, "")
assert.Contains(t, result, "- ")
}
func TestMarkdownToConfluence_OrderedList(t *testing.T) {
input := "1. First\n2. Second\n3. Third"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, "
")
}
func TestMarkdownToConfluence_NestedList(t *testing.T) {
input := "- Parent\n - Child"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Equal(t, 2, strings.Count(result, ""), "expected 2 ul tags for nested list")
}
func TestMarkdownToConfluence_HorizontalRule(t *testing.T) {
result, err := MarkdownToConfluence([]byte("---"))
require.NoError(t, err)
assert.Contains(t, result, "
")
}
func TestMarkdownToConfluence_TaskList(t *testing.T) {
input := "- [x] Done\n- [ ] Todo"
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, "")
assert.Contains(t, result, "")
assert.Contains(t, result, "")
assert.Contains(t, result, "complete")
assert.Contains(t, result, "incomplete")
assert.Contains(t, result, "")
assert.Contains(t, result, "")
}
func TestMarkdownToConfluence_Paragraph(t *testing.T) {
result, err := MarkdownToConfluence([]byte("Hello world"))
require.NoError(t, err)
assert.Contains(t, result, "Hello world
")
}
func TestMarkdownToConfluence_HardLineBreak(t *testing.T) {
result, err := MarkdownToConfluence([]byte("Line one \nLine two"))
require.NoError(t, err)
assert.Contains(t, result, "
")
}
func TestMarkdownToConfluence_InlineCommentMarker(t *testing.T) {
input := `Before commented text after`
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, ``)
assert.Contains(t, result, "")
assert.Contains(t, result, "commented text")
}
// === Confluence XML to Markdown tests ===
func TestConfluenceToMarkdown_Heading(t *testing.T) {
result, err := ConfluenceToMarkdown("Hello World
")
require.NoError(t, err)
assert.Contains(t, result, "# Hello World")
}
func TestConfluenceToMarkdown_Bold(t *testing.T) {
result, err := ConfluenceToMarkdown("bold
")
require.NoError(t, err)
assert.Contains(t, result, "**bold**")
}
func TestConfluenceToMarkdown_Italic(t *testing.T) {
result, err := ConfluenceToMarkdown("italic
")
require.NoError(t, err)
assert.Contains(t, result, "*italic*")
}
func TestConfluenceToMarkdown_Strikethrough(t *testing.T) {
result, err := ConfluenceToMarkdown("deleted
")
require.NoError(t, err)
assert.Contains(t, result, "~~deleted~~")
}
func TestConfluenceToMarkdown_InlineCode(t *testing.T) {
result, err := ConfluenceToMarkdown("code
")
require.NoError(t, err)
assert.Contains(t, result, "`code`")
}
func TestConfluenceToMarkdown_CodeBlock(t *testing.T) {
input := `python`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, "```python")
assert.Contains(t, result, `print("hello")`)
}
func TestConfluenceToMarkdown_Link(t *testing.T) {
result, err := ConfluenceToMarkdown(`Click
`)
require.NoError(t, err)
assert.Contains(t, result, "[Click](https://example.com)")
}
func TestConfluenceToMarkdown_UnorderedList(t *testing.T) {
result, err := ConfluenceToMarkdown("")
require.NoError(t, err)
assert.Contains(t, result, "- One")
assert.Contains(t, result, "- Two")
}
func TestConfluenceToMarkdown_OrderedList(t *testing.T) {
result, err := ConfluenceToMarkdown("\n- First
\n- Second
\n
")
require.NoError(t, err)
assert.Contains(t, result, "1. First")
assert.Contains(t, result, "2. Second")
}
func TestConfluenceToMarkdown_Table(t *testing.T) {
input := ``
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, "| Name |")
assert.Contains(t, result, "| Alice |")
assert.Contains(t, result, "|---|---|")
}
func TestConfluenceToMarkdown_InfoPanel(t *testing.T) {
input := `Important note
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, "> Important note")
}
func TestConfluenceToMarkdown_HorizontalRule(t *testing.T) {
result, err := ConfluenceToMarkdown("
")
require.NoError(t, err)
assert.Contains(t, result, "---")
}
func TestConfluenceToMarkdown_Emoticons(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"plus", ``, "(+)"},
{"minus", ``, "(-)"},
{"question", ``, "(?)"},
{"tick", ``, "(v)"},
{"cross", ``, "(x)"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := ConfluenceToMarkdown(tc.input)
require.NoError(t, err)
assert.Contains(t, result, tc.expected)
})
}
}
func TestConfluenceToMarkdown_InlineCommentMarker(t *testing.T) {
input := `Before commented text after
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, ``)
assert.Contains(t, result, "commented text")
assert.Contains(t, result, "")
}
// === User reference tests ===
func TestConfluenceToMarkdown_UserReference(t *testing.T) {
input := `Author:
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, ``)
}
func TestMarkdownToConfluence_UserReference(t *testing.T) {
input := `Author: `
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, ``)
}
func TestRoundTrip_UserReference(t *testing.T) {
xmlInput := `Author: wrote this
`
md, err := ConfluenceToMarkdown(xmlInput)
require.NoError(t, err)
require.Contains(t, md, `data-user-key="abc123"`)
xmlOutput, err := MarkdownToConfluence([]byte(md))
require.NoError(t, err)
assert.Contains(t, xmlOutput, `ri:userkey="abc123"`)
}
// === Attachment image tests ===
func TestConfluenceToMarkdown_AttachmentImage(t *testing.T) {
input := `
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, ``)
}
func TestConfluenceToMarkdown_AttachmentImageWithAlt(t *testing.T) {
input := `
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, `data-attachment="screenshot.png"`)
assert.Contains(t, result, `data-alt="My Screenshot"`)
}
func TestMarkdownToConfluence_AttachmentImage(t *testing.T) {
input := `Image: `
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, ``)
}
func TestMarkdownToConfluence_AttachmentImageWithAlt(t *testing.T) {
input := ``
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, `ac:alt="My Shot"`)
assert.Contains(t, result, `ri:filename="screenshot.png"`)
}
func TestRoundTrip_AttachmentImage(t *testing.T) {
xmlInput := `
`
md, err := ConfluenceToMarkdown(xmlInput)
require.NoError(t, err)
require.Contains(t, md, `data-attachment="Pasted image 20260325004147.png"`)
xmlOutput, err := MarkdownToConfluence([]byte(md))
require.NoError(t, err)
assert.Contains(t, xmlOutput, `ri:filename="Pasted image 20260325004147.png"`)
}
// === Inline spacing tests ===
func TestConfluenceToMarkdown_SpaceAfterBold(t *testing.T) {
input := ``
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, "**ATE:** Запуск плейбуков")
}
func TestConfluenceToMarkdown_SpaceBetweenInlineElements(t *testing.T) {
input := ``
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, "**Bold** then *italic* text")
}
// === Layout tests ===
func TestConfluenceToMarkdown_Layout(t *testing.T) {
input := `Content
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, "")
assert.Contains(t, result, ``)
assert.Contains(t, result, "")
assert.Contains(t, result, "Content")
assert.Contains(t, result, "")
assert.Contains(t, result, "")
assert.Contains(t, result, "")
}
func TestConfluenceToMarkdown_LayoutTwoColumn(t *testing.T) {
input := `Left
Right
`
result, err := ConfluenceToMarkdown(input)
require.NoError(t, err)
assert.Contains(t, result, `type="two_equal"`)
assert.Contains(t, result, "Left")
assert.Contains(t, result, "Right")
assert.Equal(t, 2, strings.Count(result, ""))
assert.Equal(t, 2, strings.Count(result, ""))
}
func TestMarkdownToConfluence_Layout(t *testing.T) {
input := `
Hello world
`
result, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
assert.Contains(t, result, "")
assert.Contains(t, result, ``)
assert.Contains(t, result, "")
assert.Contains(t, result, "Hello world")
assert.Contains(t, result, "")
assert.Contains(t, result, "")
assert.Contains(t, result, "")
}
func TestRoundTrip_Layout(t *testing.T) {
xmlInput := `Left column
Right column
`
md, err := ConfluenceToMarkdown(xmlInput)
require.NoError(t, err)
xmlOutput, err := MarkdownToConfluence([]byte(md))
require.NoError(t, err)
assert.Contains(t, xmlOutput, "")
assert.Contains(t, xmlOutput, `ac:type="two_equal"`)
assert.Contains(t, xmlOutput, "")
assert.Contains(t, xmlOutput, "Left column")
assert.Contains(t, xmlOutput, "Right column")
assert.Contains(t, xmlOutput, "")
assert.Contains(t, xmlOutput, "")
assert.Contains(t, xmlOutput, "")
}
// === Round-trip tests ===
func TestRoundTrip_Basic(t *testing.T) {
input := `# Test
Some **bold** and *italic* text.
## Code
` + "```go\nfunc main() {}\n```" + `
## List
- One
- Two
- Three
| H1 | H2 |
|----|-----|
| A | B |
`
xml, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
md, err := ConfluenceToMarkdown(xml)
require.NoError(t, err)
for _, check := range []string{"# Test", "**bold**", "*italic*", "```go", "func main() {}", "- One", "| H1", "| A"} {
assert.Contains(t, md, check, "round-trip should preserve %q", check)
}
}
func TestRoundTrip_Blockquote(t *testing.T) {
input := "> This is a blockquote\n"
xml, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
md, err := ConfluenceToMarkdown(xml)
require.NoError(t, err)
assert.Contains(t, md, "> This is a blockquote")
}
func TestRoundTrip_Link(t *testing.T) {
input := "[Example](https://example.com)\n"
xml, err := MarkdownToConfluence([]byte(input))
require.NoError(t, err)
md, err := ConfluenceToMarkdown(xml)
require.NoError(t, err)
assert.Contains(t, md, "[Example](https://example.com)")
}
func TestRoundTrip_InlineCommentMarker(t *testing.T) {
xmlInput := `Hello important text world
`
md, err := ConfluenceToMarkdown(xmlInput)
require.NoError(t, err)
require.Contains(t, md, `data-inline-comment="b2f6ce98-4dc9-45e0-a9b6-b4a5109657ca"`)
xmlOutput, err := MarkdownToConfluence([]byte(md))
require.NoError(t, err)
assert.Contains(t, xmlOutput, ``)
assert.Contains(t, xmlOutput, "")
assert.Contains(t, xmlOutput, "important text")
}
func TestConfluenceToMarkdown_AdjacentCodeMerge(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "directly adjacent code elements merged",
input: `helloworld
`,
expected: "`helloworld`",
},
{
name: "adjacent code with span inside",
input: `plan : vclock
`,
expected: "`plan : vclock`",
},
{
name: "single code element unchanged",
input: `single
`,
expected: "`single`",
},
{
name: "non-adjacent code elements stay separate",
input: `a text b
`,
expected: "`a` text `b`",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ConfluenceToMarkdown(tt.input)
require.NoError(t, err)
assert.Contains(t, result, tt.expected)
})
}
}
func TestRoundTrip_ListNoTrailingWhitespace(t *testing.T) {
xmlInput := ``
md, err := ConfluenceToMarkdown(xmlInput)
require.NoError(t, err)
xmlOutput, err := MarkdownToConfluence([]byte(md))
require.NoError(t, err)
assert.Contains(t, xmlOutput, "- item 1
")
assert.Contains(t, xmlOutput, "- item 2
")
assert.Contains(t, xmlOutput, "- item 3
")
assert.NotContains(t, xmlOutput, "item 1\n
")
assert.NotContains(t, xmlOutput, "item 1 ")
}
func TestRoundTrip_InlineCommentFromRealXML(t *testing.T) {
xmlInput := `Товарищ! Не майся дурью, копируй этот шаблон и редактируй его! Ускоришь написание RFC!
`
md, err := ConfluenceToMarkdown(xmlInput)
require.NoError(t, err)
xmlOutput, err := MarkdownToConfluence([]byte(md))
require.NoError(t, err)
assert.Contains(t, xmlOutput, `ac:ref="b2f6ce98-4dc9-45e0-a9b6-b4a5109657ca"`)
assert.Contains(t, xmlOutput, "Не майся дурью")
}