krz/devianter
clone: git clone https://gitbay.org/krz/devianter.git
main: comments_test.go · raw
1package devianter
2
3import "testing"
4
5// Regression: the shape check used to read m[0] and m[len(m)-1] without a length
6// check, so a comment with an empty markup body panicked with index out of range
7// and killed the caller's process.
8func TestFlattenMarkupEmptyMarkup(t *testing.T) {
9 if got := flattenMarkup(""); got != "" {
10 t.Errorf("want an empty comment for empty markup, got %q", got)
11 }
12}
13
14// tiptap is what DeviantArt actually serves today; the fixtures below are shaped
15// like responses captured from the live API.
16func TestFlattenMarkupTiptap(t *testing.T) {
17 one := `{"version":1,"document":{"type":"doc","content":[` +
18 `{"type":"paragraph","attrs":{"textAlign":"left"},"content":[` +
19 `{"type":"text","text":"Nice artwork!"}]}]}}`
20 if got := flattenMarkup(one); got != "Nice artwork!" {
21 t.Errorf("want the paragraph's text, got %q", got)
22 }
23
24 // Regression: DeviantArt sends "version" as a number on some bodies and a
25 // string on others. Modelling it as either type fails to unmarshal half of
26 // them, so it must not be modelled at all.
27 stringVersion := `{"version":"1","document":{"type":"doc","content":[` +
28 `{"type":"paragraph","content":[{"type":"text","text":"hello"}]}]}}`
29 if got := flattenMarkup(stringVersion); got != "hello" {
30 t.Errorf(`want a string "version" handled the same as a numeric one, got %q`, got)
31 }
32
33 // Each block is its own line.
34 two := `{"version":1,"document":{"type":"doc","content":[` +
35 `{"type":"paragraph","content":[{"type":"text","text":"first"}]},` +
36 `{"type":"paragraph","content":[{"type":"text","text":"second"}]}]}}`
37 if got, want := flattenMarkup(two), "first\nsecond"; got != want {
38 t.Errorf("want one line per block:\n got %q\nwant %q", got, want)
39 }
40
41 // An empty paragraph is a blank line, not something to skip.
42 blank := `{"version":1,"document":{"type":"doc","content":[` +
43 `{"type":"paragraph","content":[{"type":"text","text":"a"}]},` +
44 `{"type":"paragraph"},` +
45 `{"type":"paragraph","content":[{"type":"text","text":"b"}]}]}}`
46 if got, want := flattenMarkup(blank), "a\n\nb"; got != want {
47 t.Errorf("want an empty paragraph preserved as a blank line:\n got %q\nwant %q", got, want)
48 }
49
50 // A hard break is a newline within its block.
51 brk := `{"version":1,"document":{"type":"doc","content":[` +
52 `{"type":"paragraph","content":[{"type":"text","text":"up"},` +
53 `{"type":"hardBreak"},{"type":"text","text":"down"}]}]}}`
54 if got, want := flattenMarkup(brk), "up\ndown"; got != want {
55 t.Errorf("want a hardBreak as a newline:\n got %q\nwant %q", got, want)
56 }
57
58 // Marked-up runs are separate text nodes and must be concatenated, not
59 // separated.
60 marks := `{"version":1,"document":{"type":"doc","content":[` +
61 `{"type":"paragraph","content":[` +
62 `{"type":"text","text":"plain "},` +
63 `{"type":"text","marks":[{"type":"bold"}],"text":"bold"},` +
64 `{"type":"text","text":" tail"}]}]}}`
65 if got, want := flattenMarkup(marks), "plain bold tail"; got != want {
66 t.Errorf("want marked runs concatenated:\n got %q\nwant %q", got, want)
67 }
68
69 // Text carrying a link mark still surfaces; the href does not.
70 link := `{"version":1,"document":{"type":"doc","content":[` +
71 `{"type":"paragraph","content":[{"type":"text","marks":[` +
72 `{"type":"link","attrs":{"href":"https://example.com"}}],"text":"click here"}]}]}}`
73 if got, want := flattenMarkup(link), "click here"; got != want {
74 t.Errorf("want the link text without the href:\n got %q\nwant %q", got, want)
75 }
76
77 // Headings are blocks like any other.
78 heading := `{"version":1,"document":{"type":"doc","content":[` +
79 `{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Title"}]},` +
80 `{"type":"paragraph","content":[{"type":"text","text":"body"}]}]}}`
81 if got, want := flattenMarkup(heading), "Title\nbody"; got != want {
82 t.Errorf("want a heading flattened as a block:\n got %q\nwant %q", got, want)
83 }
84
85 // Bodies carry HTML entities on the wire; plain text should not.
86 entity := `{"version":1,"document":{"type":"doc","content":[` +
87 `{"type":"paragraph","content":[{"type":"text","text":"I’d love & more"}]}]}}`
88 if got, want := flattenMarkup(entity), "I’d love & more"; got != want {
89 t.Errorf("want HTML entities decoded:\n got %q\nwant %q", got, want)
90 }
91
92 // A node carrying no text of its own contributes nothing.
93 emote := `{"version":1,"document":{"type":"doc","content":[` +
94 `{"type":"paragraph","content":[{"type":"text","text":"hi "},` +
95 `{"type":"da-emote","attrs":{"name":":happy:"}}]}]}}`
96 if got, want := flattenMarkup(emote), "hi "; got != want {
97 t.Errorf("want a textless node to contribute nothing:\n got %q\nwant %q", got, want)
98 }
99}
100
101// Draft.js is legacy but still served on old bodies, so the path stays.
102func TestFlattenMarkupDraftJS(t *testing.T) {
103 draft := `{"blocks":[{"text":"hello there"}]}`
104 if got := flattenMarkup(draft); got != "hello there" {
105 t.Errorf("want the Draft.js block text, got %q", got)
106 }
107
108 // Regression: the block loop used to assign rather than accumulate, so every
109 // block but the last was silently dropped and a multi-paragraph comment came
110 // back as its closing line only.
111 multi := `{"blocks":[{"text":"first"},{"text":"second"},{"text":"third"}]}`
112 if got, want := flattenMarkup(multi), "first\nsecond\nthird"; got != want {
113 t.Errorf("want every block, one per line:\n got %q\nwant %q", got, want)
114 }
115
116 // An empty block is a blank line, not something to skip.
117 blank := `{"blocks":[{"text":"first"},{"text":""},{"text":"third"}]}`
118 if got, want := flattenMarkup(blank), "first\n\nthird"; got != want {
119 t.Errorf("want an empty block preserved as a blank line:\n got %q\nwant %q", got, want)
120 }
121}
122
123func TestFlattenMarkupPassthrough(t *testing.T) {
124 // An older, plain-HTML body passes through untouched.
125 html := "<b>hello</b> there"
126 if got := flattenMarkup(html); got != html {
127 t.Errorf("want plain HTML passed through, got %q", got)
128 }
129
130 // Brace-shaped markup in no known format falls back to itself rather than to
131 // an empty string.
132 if got := flattenMarkup("{}"); got != "{}" {
133 t.Errorf("want the original markup when nothing parses, got %q", got)
134 }
135
136 // Well-formed JSON that is neither format is still not silently eaten.
137 other := `{"something":"else"}`
138 if got := flattenMarkup(other); got != other {
139 t.Errorf("want unrecognised JSON passed through, got %q", got)
140 }
141
142 // A single brace satisfies neither end of the shape check.
143 if got := flattenMarkup("{"); got != "{" {
144 t.Errorf("want a lone brace passed through, got %q", got)
145 }
146}