Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 29m ago 32290114
history blame raw 2.7 KiB
1package web
2
3import (
4 "testing"
5
6 "rodent.software/vole/metadata"
7)
8
9// The thread is derived from data a push can write, so the shapes that matter are
10// the awkward ones: a reply to a reply, a reply to a deleted comment, a reply whose
11// parent is gone, and a cycle nobody intended.
12
13func ids(views []commentView) []string {
14 out := make([]string, 0, len(views))
15 for _, v := range views {
16 out = append(out, v.ID)
17 }
18 return out
19}
20
21func TestThreadCommentsFlattensRepliesUnderTheRoot(t *testing.T) {
22 comments := []commentView{
23 {Comment: metadata.Comment{ID: "03", Author: "c", ReplyTo: "02"}},
24 {Comment: metadata.Comment{ID: "01", Author: "a"}},
25 {Comment: metadata.Comment{ID: "02", Author: "b", ReplyTo: "01"}},
26 }
27 thread := threadComments(comments)
28 if len(thread) != 1 || thread[0].ID != "01" {
29 t.Fatalf("roots = %v, want just 01", ids(thread))
30 }
31 // A reply to a reply is still shown under the root, and says what it answers.
32 if got := ids(thread[0].Replies); len(got) != 2 || got[0] != "02" || got[1] != "03" {
33 t.Fatalf("replies = %v, want [02 03]", got)
34 }
35 if thread[0].Replies[1].ReplyToName != "b" {
36 t.Fatalf("ReplyToName = %q, want b", thread[0].Replies[1].ReplyToName)
37 }
38}
39
40func TestThreadCommentsKeepsTombstones(t *testing.T) {
41 comments := []commentView{
42 {Comment: metadata.Comment{ID: "02", Author: "b", ReplyTo: "01"}},
43 }
44 thread := threadComments(comments)
45 if len(thread) != 1 || thread[0].ID != "01" || !thread[0].Deleted {
46 t.Fatalf("missing parent did not become a tombstone: %+v", thread)
47 }
48 if len(thread[0].Replies) != 1 || !thread[0].Replies[0].ReplyToDeleted {
49 t.Fatalf("reply does not know its parent was deleted: %+v", thread[0].Replies)
50 }
51}
52
53func TestThreadCommentsSurvivesCycles(t *testing.T) {
54 // Two comments answering each other have no root. Neither may vanish, and the
55 // walk must terminate.
56 comments := []commentView{
57 {Comment: metadata.Comment{ID: "01", ReplyTo: "02"}},
58 {Comment: metadata.Comment{ID: "02", ReplyTo: "01"}},
59 {Comment: metadata.Comment{ID: "03"}},
60 }
61 thread := threadComments(comments)
62 seen := map[string]bool{}
63 for _, root := range thread {
64 seen[root.ID] = true
65 for _, reply := range root.Replies {
66 seen[reply.ID] = true
67 }
68 }
69 for _, id := range []string{"01", "02", "03"} {
70 if !seen[id] {
71 t.Fatalf("comment %s disappeared from the thread: %+v", id, thread)
72 }
73 }
74}
75
76// A comment that answers itself is not a thread; it is a root.
77func TestThreadCommentsTreatsASelfReplyAsARoot(t *testing.T) {
78 thread := threadComments([]commentView{{Comment: metadata.Comment{ID: "01", ReplyTo: "01"}}})
79 if len(thread) != 1 || thread[0].ID != "01" || len(thread[0].Replies) != 0 {
80 t.Fatalf("self reply = %+v", thread)
81 }
82}
83