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
|
package devianter
import "testing"
// Regression: the shape check used to read m[0] and m[len(m)-1] without a length
// check, so a comment with an empty markup body panicked with index out of range
// and killed the caller's process.
func TestFlattenMarkupEmptyMarkup(t *testing.T) {
if got := flattenMarkup(""); got != "" {
t.Errorf("want an empty comment for empty markup, got %q", got)
}
}
// tiptap is what DeviantArt actually serves today; the fixtures below are shaped
// like responses captured from the live API.
func TestFlattenMarkupTiptap(t *testing.T) {
one := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","attrs":{"textAlign":"left"},"content":[` +
`{"type":"text","text":"Nice artwork!"}]}]}}`
if got := flattenMarkup(one); got != "Nice artwork!" {
t.Errorf("want the paragraph's text, got %q", got)
}
// Regression: DeviantArt sends "version" as a number on some bodies and a
// string on others. Modelling it as either type fails to unmarshal half of
// them, so it must not be modelled at all.
stringVersion := `{"version":"1","document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}}`
if got := flattenMarkup(stringVersion); got != "hello" {
t.Errorf(`want a string "version" handled the same as a numeric one, got %q`, got)
}
// Each block is its own line.
two := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","text":"first"}]},` +
`{"type":"paragraph","content":[{"type":"text","text":"second"}]}]}}`
if got, want := flattenMarkup(two), "first\nsecond"; got != want {
t.Errorf("want one line per block:\n got %q\nwant %q", got, want)
}
// An empty paragraph is a blank line, not something to skip.
blank := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","text":"a"}]},` +
`{"type":"paragraph"},` +
`{"type":"paragraph","content":[{"type":"text","text":"b"}]}]}}`
if got, want := flattenMarkup(blank), "a\n\nb"; got != want {
t.Errorf("want an empty paragraph preserved as a blank line:\n got %q\nwant %q", got, want)
}
// A hard break is a newline within its block.
brk := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","text":"up"},` +
`{"type":"hardBreak"},{"type":"text","text":"down"}]}]}}`
if got, want := flattenMarkup(brk), "up\ndown"; got != want {
t.Errorf("want a hardBreak as a newline:\n got %q\nwant %q", got, want)
}
// Marked-up runs are separate text nodes and must be concatenated, not
// separated.
marks := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[` +
`{"type":"text","text":"plain "},` +
`{"type":"text","marks":[{"type":"bold"}],"text":"bold"},` +
`{"type":"text","text":" tail"}]}]}}`
if got, want := flattenMarkup(marks), "plain bold tail"; got != want {
t.Errorf("want marked runs concatenated:\n got %q\nwant %q", got, want)
}
// Text carrying a link mark still surfaces; the href does not.
link := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","marks":[` +
`{"type":"link","attrs":{"href":"https://example.com"}}],"text":"click here"}]}]}}`
if got, want := flattenMarkup(link), "click here"; got != want {
t.Errorf("want the link text without the href:\n got %q\nwant %q", got, want)
}
// Headings are blocks like any other.
heading := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Title"}]},` +
`{"type":"paragraph","content":[{"type":"text","text":"body"}]}]}}`
if got, want := flattenMarkup(heading), "Title\nbody"; got != want {
t.Errorf("want a heading flattened as a block:\n got %q\nwant %q", got, want)
}
// Bodies carry HTML entities on the wire; plain text should not.
entity := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","text":"I’d love & more"}]}]}}`
if got, want := flattenMarkup(entity), "I’d love & more"; got != want {
t.Errorf("want HTML entities decoded:\n got %q\nwant %q", got, want)
}
// A node carrying no text of its own contributes nothing.
emote := `{"version":1,"document":{"type":"doc","content":[` +
`{"type":"paragraph","content":[{"type":"text","text":"hi "},` +
`{"type":"da-emote","attrs":{"name":":happy:"}}]}]}}`
if got, want := flattenMarkup(emote), "hi "; got != want {
t.Errorf("want a textless node to contribute nothing:\n got %q\nwant %q", got, want)
}
}
// Draft.js is legacy but still served on old bodies, so the path stays.
func TestFlattenMarkupDraftJS(t *testing.T) {
draft := `{"blocks":[{"text":"hello there"}]}`
if got := flattenMarkup(draft); got != "hello there" {
t.Errorf("want the Draft.js block text, got %q", got)
}
// Regression: the block loop used to assign rather than accumulate, so every
// block but the last was silently dropped and a multi-paragraph comment came
// back as its closing line only.
multi := `{"blocks":[{"text":"first"},{"text":"second"},{"text":"third"}]}`
if got, want := flattenMarkup(multi), "first\nsecond\nthird"; got != want {
t.Errorf("want every block, one per line:\n got %q\nwant %q", got, want)
}
// An empty block is a blank line, not something to skip.
blank := `{"blocks":[{"text":"first"},{"text":""},{"text":"third"}]}`
if got, want := flattenMarkup(blank), "first\n\nthird"; got != want {
t.Errorf("want an empty block preserved as a blank line:\n got %q\nwant %q", got, want)
}
}
func TestFlattenMarkupPassthrough(t *testing.T) {
// An older, plain-HTML body passes through untouched.
html := "<b>hello</b> there"
if got := flattenMarkup(html); got != html {
t.Errorf("want plain HTML passed through, got %q", got)
}
// Brace-shaped markup in no known format falls back to itself rather than to
// an empty string.
if got := flattenMarkup("{}"); got != "{}" {
t.Errorf("want the original markup when nothing parses, got %q", got)
}
// Well-formed JSON that is neither format is still not silently eaten.
other := `{"something":"else"}`
if got := flattenMarkup(other); got != other {
t.Errorf("want unrecognised JSON passed through, got %q", got)
}
// A single brace satisfies neither end of the shape check.
if got := flattenMarkup("{"); got != "{" {
t.Errorf("want a lone brace passed through, got %q", got)
}
}
|