~bigbes/confluence-md-utilities

ref: f6c846a1f16d1f6d0045c92a1c0f24429e6b901f confluence-md-utilities/format/pretty_test.go -rw-r--r-- 8.9 KiB
f6c846a1 — Eugene Blikh fix: preserve inline spacing in fmt and code-block round-trip a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package format

import (
	"strings"
	"testing"
	"unicode/utf8"

	"github.com/stretchr/testify/assert"
)

func TestPrettyXML_Paragraph(t *testing.T) {
	input := `<p>Hello <strong>world</strong></p>`
	result := PrettyXML(input, "  ")
	assert.Equal(t, "<p>\n  Hello <strong>world</strong>\n</p>\n", result)
}

func TestPrettyXML_NestedBlocks(t *testing.T) {
	input := `<ul><li>One</li><li>Two</li></ul>`
	result := PrettyXML(input, "  ")
	// li should be inlined since content is short
	assert.Contains(t, result, "  <li>One</li>\n")
	assert.Contains(t, result, "  <li>Two</li>\n")
}

func TestPrettyXML_InlineStaysInline(t *testing.T) {
	input := `<p>Text with <strong>bold</strong> and <em>italic</em></p>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "Text with <strong>bold</strong> and <em>italic</em>")
}

func TestPrettyXML_CodeBlockCDATAPreserved(t *testing.T) {
	input := `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">go</ac:parameter><ac:plain-text-body><![CDATA[func main() {
    fmt.Println("hello")
}]]></ac:plain-text-body></ac:structured-macro>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, `<![CDATA[func main() {
    fmt.Println("hello")
}]]>`)
}

func TestPrettyXML_HeadingsInlined(t *testing.T) {
	input := `<h1>Title</h1><h2>Subtitle</h2>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "<h1>Title</h1>\n")
	assert.Contains(t, result, "<h2>Subtitle</h2>\n")
}

func TestPrettyXML_HeadingsWithInlineMarkup(t *testing.T) {
	input := `<h2>Section <strong>Important</strong></h2>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "<h2>Section <strong>Important</strong></h2>\n")
}

func TestPrettyXML_SelfClosingBlock(t *testing.T) {
	input := `<p>Before</p><hr/><p>After</p>`
	result := PrettyXML(input, "  ")
	// Formatter normalizes self-closing tags to "<tag />" form.
	assert.Contains(t, result, "<hr />\n")
}

func TestPrettyXML_Table(t *testing.T) {
	input := `<table><tbody><tr><th><p>Name</p></th><td><p>Value</p></td></tr></tbody></table>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "<table>\n")
	assert.Contains(t, result, "  <tbody>\n")
	assert.Contains(t, result, "    <tr>\n")
}

func TestPrettyXML_Layout(t *testing.T) {
	input := `<ac:layout><ac:layout-section ac:type="single"><ac:layout-cell><p>Content</p></ac:layout-cell></ac:layout-section></ac:layout>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "<ac:layout>\n")
	assert.Contains(t, result, "  <ac:layout-section ac:type=\"single\">\n")
	assert.Contains(t, result, "    <ac:layout-cell>\n")
}

func TestPrettyXML_TaskList(t *testing.T) {
	input := `<ac:task-list><ac:task><ac:task-id>1</ac:task-id><ac:task-status>complete</ac:task-status><ac:task-body>Done</ac:task-body></ac:task></ac:task-list>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "<ac:task-list>\n")
	assert.Contains(t, result, "  <ac:task>\n")
	// task-id and task-status should be inlined
	assert.Contains(t, result, "<ac:task-id>1</ac:task-id>")
	assert.Contains(t, result, "<ac:task-status>complete</ac:task-status>")
}

func TestPrettyXML_CommentsPreserved(t *testing.T) {
	input := `<!-- MD_CONTENT_START --><p>Content</p><!-- MD_CONTENT_END -->`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "<!-- MD_CONTENT_START -->")
	assert.Contains(t, result, "<!-- MD_CONTENT_END -->")
}

func TestPrettyXML_InlineElements(t *testing.T) {
	input := `<p>Link: <a href="https://example.com">click</a> and <code>code</code></p>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, `Link: <a href="https://example.com">click</a> and <code>code</code>`)
}

func TestPrettyXML_EmptyInput(t *testing.T) {
	result := PrettyXML("", "  ")
	assert.Equal(t, "\n", result)
}

func TestPrettyXML_UserAndAttachmentInline(t *testing.T) {
	input := `<p>By <ac:link><ri:user ri:userkey="abc123"/></ac:link> see <ac:image><ri:attachment ri:filename="img.png"/></ac:image></p>`
	result := PrettyXML(input, "  ")
	// Formatter normalizes self-closing tags to "<tag />" form.
	assert.Contains(t, result, `<ac:link><ri:user ri:userkey="abc123" /></ac:link>`)
}

func TestPrettyXML_CustomIndent(t *testing.T) {
	input := `<ul><li>Item</li></ul>`
	result := PrettyXML(input, "\t")
	assert.Contains(t, result, "\t<li>Item</li>")
}

func TestPrettyXML_LiInlinedShort(t *testing.T) {
	input := `<ul><li>Short item</li></ul>`
	result := PrettyXML(input, "  ")
	assert.Contains(t, result, "  <li>Short item</li>\n")
}

func TestPrettyXML_LiExpandedLong(t *testing.T) {
	longText := strings.Repeat("слово ", 30) // ~180 chars in Cyrillic
	input := `<ul><li>` + longText + `</li></ul>`
	result := PrettyXML(input, "  ")
	// Should NOT be inlined — too long
	assert.Contains(t, result, "  <li>\n")
}

func TestPrettyXML_LongLineWrapped(t *testing.T) {
	longText := strings.Repeat("word ", 30) // 150 chars
	input := `<p>` + longText + `</p>`
	result := PrettyXML(input, "  ")
	for _, line := range strings.Split(result, "\n") {
		w := utf8.RuneCountInString(line)
		if w > 125 { // allow small overshoot for tags
			t.Errorf("line too long (%d runes): %s", w, line)
		}
	}
}

func TestPrettyXML_LongLineUTF8(t *testing.T) {
	// Russian text: each Cyrillic char is 1 rune but 2 bytes
	longText := strings.Repeat("Привет мир ", 15) // ~165 runes
	input := `<p>` + longText + `</p>`
	result := PrettyXML(input, "  ")
	for _, line := range strings.Split(result, "\n") {
		w := utf8.RuneCountInString(line)
		if w > 125 {
			t.Errorf("line too long (%d runes): %s", w, line[:80])
		}
	}
}

func TestPrettyXML_LongLinePreservesTagIntegrity(t *testing.T) {
	input := `<p>Text <strong>bold text here</strong> more text and <a href="https://example.com/very/long/path">some link</a> even more text to make line long enough to wrap around the boundary limit</p>`
	result := PrettyXML(input, "  ")
	// Tags should not be split across lines
	assert.NotContains(t, result, "<strong\n")
	assert.NotContains(t, result, "</strong\n")
}

// A single space between two adjacent inline elements is significant and must
// survive formatting. It used to be dropped as a presumed indentation artifact,
// gluing the elements together (e.g. "</strong><code>"). Regression for the
// "missing space before inline code" report.
func TestPrettyXML_SpaceBetweenInlineElements(t *testing.T) {
	cases := []struct{ input, want string }{
		{`<p>Bold <strong>word</strong> <code>code</code> here.</p>`, "</strong> <code>"},
		{`<p>Link <a href="http://x">t</a> <code>code</code> here.</p>`, "</a> <code>"},
		{`<p>Word <em>em</em> <code>code</code>.</p>`, "</em> <code>"},
	}
	for _, tc := range cases {
		result := PrettyXML(tc.input, "  ")
		assert.Contains(t, result, tc.want, tc.input)
	}
}

// The same significant space must also survive when the line is long enough to
// be wrapped: wrapLine used to drop whitespace that sat between a word/tag and
// an adjacent tag, because strings.Fields discarded trailing space and tags
// ignored any pending space.
func TestPrettyXML_WrapPreservesSpaceBeforeTag(t *testing.T) {
	long := strings.Repeat("слово ", 25) // push past the 120-rune wrap width
	input := `<p>` + long + `<strong>жирный</strong> <code>код</code> хвост предложения.</p>`
	result := PrettyXML(input, "  ")
	assert.NotContains(t, result, "</strong><code>")
	assert.Contains(t, result, "</strong> <code>")
}

// The literal reported pattern: a plain word followed by inline code on a line
// long enough to wrap. wrapLine's strings.Fields dropped the word's trailing
// space, gluing it to <code> ("манифеста<code>" instead of "манифеста <code>").
func TestPrettyXML_WrapPreservesSpaceBeforeCodeAfterWord(t *testing.T) {
	long := strings.Repeat("текст ", 25) // push past the 120-rune wrap width
	input := `<p>` + long + `манифеста <code>app.manifest.toml</code> и далее.</p>`
	result := PrettyXML(input, "  ")
	assert.NotContains(t, result, "манифеста<code>")
	assert.Contains(t, result, "манифеста <code>")
}

// A wrap must never fall inside <code>...</code>: doing so injects the
// continuation indent into the code text, which round-trips as a spurious space
// inside the span. The whole span is kept on one line regardless of where the
// boundary lands. Probes several lengths so the wrap point sweeps across the
// code span's open tag.
func TestPrettyXML_WrapKeepsCodeSpanWhole(t *testing.T) {
	for n := 18; n <= 24; n++ {
		input := `<p>` + strings.Repeat("word ", n) + `tail <code>app.manifest.lock</code> rest of it.</p>`
		result := PrettyXML(input, "  ")
		for _, line := range strings.Split(result, "\n") {
			trimmed := strings.TrimSpace(line)
			// No line may start or end mid-span (open without close, or vice versa).
			assert.False(t, strings.HasPrefix(trimmed, "app.manifest.lock"),
				"code content split onto its own line at n=%d: %q", n, line)
			if strings.Contains(line, "<code>") {
				assert.Contains(t, line, "</code>",
					"<code> opened but not closed on same line at n=%d: %q", n, line)
			}
		}
	}
}