talk_test.gno
4.71 Kb · 136 lines
1package wiki
2
3import (
4 "strings"
5 "testing"
6
7 "gno.land/p/nt/uassert/v0"
8)
9
10func comment(t *testing.T, w *Wiki, n int, author address, title, body string, replyTo uint64) *Comment {
11 t.Helper()
12 now, h := at(n)
13 c, err := w.Comment(author, now, h, title, body, replyTo)
14 uassert.NoError(t, err, title)
15 return c
16}
17
18func TestCommentThreads(t *testing.T) {
19 w := New(3, DefaultMaxBody)
20 edit(t, w, 1, alice, "A", "the article\n", "")
21
22 root1 := comment(t, w, 2, alice, "A", "First point.\n", 0)
23 root2 := comment(t, w, 3, bob, "A", "Second point.\n", 0)
24 reply := comment(t, w, 4, mod, "A", "Answering the first.\n", root1.ID)
25
26 title := MustParseTitle("A")
27 uassert.Equal(t, 3, w.NumComments(title))
28
29 threads := w.Comments(title, 0, 10)
30 uassert.Equal(t, 2, len(threads), "replies do not count as threads")
31 uassert.Equal(t, uint64(root1.ID), threads[0].Root.ID)
32 uassert.Equal(t, 1, len(threads[0].Replies))
33 uassert.Equal(t, uint64(reply.ID), threads[0].Replies[0].ID)
34 uassert.Equal(t, uint64(root2.ID), threads[1].Root.ID)
35 uassert.Equal(t, 0, len(threads[1].Replies))
36
37 uassert.Equal(t, 1, len(w.Comments(title, 1, 10)), "offset skips whole threads")
38}
39
40func TestCommentRejectsBadInput(t *testing.T) {
41 w := New(3, DefaultMaxBody)
42 edit(t, w, 1, alice, "A", "the article\n", "")
43 now, h := at(2)
44
45 _, err := w.Comment(alice, now, h, "Missing page", "hi\n", 0)
46 uassert.ErrorIs(t, err, ErrNoSuchPage, "a discussion needs a page to be about")
47
48 _, err = w.Comment(alice, now, h, "A", "", 0)
49 uassert.ErrorIs(t, err, ErrEmptyComment)
50
51 _, err = w.Comment(alice, now, h, "A", strings.Repeat("x", MaxCommentLen+1), 0)
52 uassert.ErrorIs(t, err, ErrCommentTooLong)
53
54 _, err = w.Comment(alice, now, h, "A", "hi\n", 999)
55 uassert.ErrorIs(t, err, ErrNoSuchComment)
56}
57
58// TestRepliesNestOneLevel pins MaxReplyDepth. Unbounded nesting means
59// unbounded recursive rendering, which is the shape the query gas ceiling
60// punishes hardest.
61func TestRepliesNestOneLevel(t *testing.T) {
62 w := New(3, DefaultMaxBody)
63 edit(t, w, 1, alice, "A", "the article\n", "")
64 root := comment(t, w, 2, alice, "A", "root\n", 0)
65 reply := comment(t, w, 3, bob, "A", "reply\n", root.ID)
66
67 now, h := at(4)
68 _, err := w.Comment(mod, now, h, "A", "reply to a reply\n", reply.ID)
69 uassert.ErrorIs(t, err, ErrReplyToReply)
70}
71
72func TestHideComment(t *testing.T) {
73 w := New(3, DefaultMaxBody)
74 edit(t, w, 1, alice, "A", "the article\n", "")
75 before := w.Stats().BytesHeld
76 c := comment(t, w, 2, bob, "A", "unpleasant\n", 0)
77 uassert.Equal(t, before+len("unpleasant\n"), w.Stats().BytesHeld,
78 "a comment costs its author the bytes it adds")
79
80 released, err := w.HideComment("A", c.ID)
81 uassert.NoError(t, err)
82 uassert.Equal(t, len("unpleasant\n"), released)
83 uassert.Equal(t, before, w.Stats().BytesHeld)
84
85 threads := w.Comments(MustParseTitle("A"), 0, 10)
86 uassert.Equal(t, 1, len(threads), "a hidden message stays in the thread")
87 uassert.True(t, threads[0].Root.Hidden)
88 uassert.Equal(t, "", threads[0].Root.Body)
89
90 // Hiding twice must not double-count the refund.
91 released, err = w.HideComment("A", c.ID)
92 uassert.NoError(t, err)
93 uassert.Equal(t, 0, released)
94 uassert.Equal(t, before, w.Stats().BytesHeld)
95}
96
97func TestRenderTalk(t *testing.T) {
98 w := New(3, DefaultMaxBody)
99 edit(t, w, 1, alice, "A", "the article\n", "")
100 root := comment(t, w, 2, alice, "A", "Is [[B]] the right link?\n", 0)
101 comment(t, w, 3, bob, "A", "Yes.\n", root.ID)
102 hidden := comment(t, w, 4, mod, "A", "spam\n", 0)
103 w.HideComment("A", hidden.ID)
104
105 p, _ := w.Page("A")
106 c := Ctx{Base: "/r/moul/x/wiki/v0", Exists: w.Exists}
107 out := RenderTalk(c, w, p, 0, 10, act)
108
109 uassert.True(t, strings.Contains(out, "# Discussion: A"))
110 uassert.True(t, strings.Contains(out, "func=Comment"), "an add-a-message action")
111 uassert.True(t, strings.Contains(out, "Yes."), "replies render under their root")
112 uassert.True(t, strings.Contains(out, "removed by a steward"))
113 uassert.False(t, strings.Contains(out, "spam"), "a hidden body is gone from the render")
114
115 // A comment is user markdown too: it goes through the sanitizer, and it
116 // is NOT a wikilink slot, so brackets stay literal rather than linking.
117 uassert.True(t, strings.Contains(out, `\[\[B\]\]`), out)
118}
119
120func TestRenderTalkEmpty(t *testing.T) {
121 w := New(3, DefaultMaxBody)
122 edit(t, w, 1, alice, "A", "the article\n", "")
123 p, _ := w.Page("A")
124 out := RenderTalk(Ctx{Base: "/r/moul/x/wiki/v0"}, w, p, 0, 10, nil)
125 uassert.True(t, strings.Contains(out, "No messages yet"))
126}
127
128func TestArticleLinksToItsDiscussion(t *testing.T) {
129 w := New(3, DefaultMaxBody)
130 edit(t, w, 1, alice, "A", "the article\n", "")
131 comment(t, w, 2, alice, "A", "hi\n", 0)
132
133 p, _ := w.Page("A")
134 out := RenderArticle(Ctx{Base: "/r/moul/x/wiki/v0"}, w, p, nil)
135 uassert.True(t, strings.Contains(out, "[discussion](/r/moul/x/wiki/v0:A/talk) (1)"), out)
136}