]> git.giacomolli.uk - onechat/blob - view_test.go
Build the nodes from a queue in the background
[onechat] / view_test.go
1 package main
2
3 import (
4         "slices"
5         "strings"
6         "testing"
7
8         "github.com/stretchr/testify/require"
9 )
10
11 func TestViewMergesInTheOrderOfTaelinsPush(t *testing.T) {
12         tree := builtTree{}
13         view := newView(0, 0)
14         var states *pushEntry
15         for tick := range 20001 {
16                 states = push(tick, states)
17                 starts := states.starts()
18                 view.high = len(starts)
19                 view.low = len(starts)
20                 view.Append(tree)
21
22                 lines := make([]int, len(view.lines))
23                 for n, line := range view.lines {
24                         lines[n] = line.index << line.level
25                 }
26
27                 require.Equal(t, starts, lines, "t=%d", tick)
28         }
29 }
30
31 func TestViewMergesInOneBatchFromHighToLow(t *testing.T) {
32         tree := builtTree{}
33         view := newView(10, 5)
34         sizes := make([]int, 0, 20)
35         for range 20 {
36                 view.Append(tree)
37                 sizes = append(sizes, view.Size(tree))
38         }
39
40         require.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 5, 6, 7, 8, 9, 10, 5, 6, 7, 8}, sizes)
41 }
42
43 func TestViewMergesOnlyBuiltParents(t *testing.T) {
44         tree := partTree{
45                 sizes: map[nodeKey]int{
46                         {0, 0}: 1,
47                         {0, 1}: 1,
48                         {0, 2}: 1,
49                         {0, 3}: 1,
50                         {0, 4}: 1,
51                         {0, 5}: 1,
52                 },
53         }
54
55         view := newView(3, 2)
56         for range 4 {
57                 view.Append(tree)
58         }
59
60         require.Equal(t, []nodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, view.lines)
61         require.True(t, view.merging)
62
63         tree.sizes[nodeKey{1, 0}] = 1
64         view.Append(tree)
65         require.Equal(t, []nodeKey{{1, 0}, {0, 2}, {0, 3}, {0, 4}}, view.lines)
66         require.True(t, view.merging)
67
68         tree.sizes[nodeKey{1, 1}] = 1
69         tree.sizes[nodeKey{1, 2}] = 1
70         tree.sizes[nodeKey{2, 0}] = 1
71         view.Append(tree)
72         require.Equal(t, []nodeKey{{2, 0}, {1, 2}}, view.lines)
73         require.False(t, view.merging)
74 }
75
76 func TestViewRendersPendingLinesWithoutTheirMessage(t *testing.T) {
77         tree := partTree{
78                 sizes: map[nodeKey]int{
79                         {0, 0}: 3,
80                         {0, 1}: 3,
81                         {1, 0}: 7,
82                 },
83         }
84
85         view, err := restoreView(viewState{
86                 Lines: [][2]int{{1, 0}, {0, 2}},
87         }, chatHigh, chatLow, tree)
88         require.NoError(t, err)
89         require.Equal(t, "<chat>\n0+2|aaa aaa\n2+1|"+pending+"\n</chat>", view.Render(tree))
90         require.Equal(t, 7+len(pending), view.Size(tree))
91 }
92
93 func TestViewCoarsenTakesTheCoarserLines(t *testing.T) {
94         view, err := restoreView(viewState{
95                 Lines: [][2]int{{1, 0}, {0, 2}, {0, 3}, {0, 4}, {0, 5}},
96         }, 100, 100, builtTree{})
97         require.NoError(t, err)
98
99         other, err := restoreView(viewState{
100                 Lines: [][2]int{{0, 0}, {0, 1}, {1, 1}, {0, 4}, {0, 5}},
101         }, 100, 100, builtTree{})
102         require.NoError(t, err)
103
104         view.Coarsen(other, builtTree{})
105         require.Equal(t, []nodeKey{{1, 0}, {1, 1}, {0, 4}, {0, 5}}, view.lines)
106
107         view.low = 2
108         view.Coarsen(other, builtTree{})
109         require.Equal(t, []nodeKey{{2, 0}, {1, 2}}, view.lines)
110         require.False(t, view.merging)
111 }
112
113 func TestViewContextStopsAtTheFirstPendingLine(t *testing.T) {
114         tree := partTree{
115                 sizes: map[nodeKey]int{
116                         {0, 0}: 1,
117                         {0, 1}: 1,
118                         {1, 0}: 1,
119                         {0, 3}: 1,
120                 },
121         }
122
123         view, err := restoreView(viewState{
124                 Lines: [][2]int{{1, 0}, {0, 2}, {0, 3}},
125         }, 100, 100, tree)
126         require.NoError(t, err)
127         require.Equal(t, "<chat>\n</chat>", view.Context(tree, 0))
128         require.Equal(t, "<chat>\n0+2|a\n</chat>", view.Context(tree, 2))
129         require.Equal(t, "<chat>\n0+2|a\n</chat>", view.Context(tree, 3))
130 }
131
132 func TestRestoreViewRejectsAGap(t *testing.T) {
133         _, err := restoreView(viewState{
134                 Lines: [][2]int{{0, 0}, {0, 2}},
135         }, chatHigh, chatLow, builtTree{})
136         require.ErrorContains(t, err, "does not start at message 1")
137 }
138
139 // pushEntry is one entry of Taelin's rollback state list, newest first. Under
140 // push alone, life stays 0, so the entry leaves it out.
141 type pushEntry struct {
142         keep  bool
143         state int
144         older *pushEntry
145 }
146
147 // push is the push of rollback_state_list.js.
148 func push(state int, states *pushEntry) *pushEntry {
149         if states == nil {
150                 return &pushEntry{
151                         state: state,
152                 }
153         }
154
155         if !states.keep {
156                 return &pushEntry{
157                         keep:  true,
158                         state: states.state,
159                         older: states.older,
160                 }
161         }
162
163         return &pushEntry{
164                 state: state,
165                 older: push(states.state, states.older),
166         }
167 }
168
169 // starts returns the states of the list, oldest first.
170 func (e *pushEntry) starts() []int {
171         var starts []int
172         for entry := e; entry != nil; entry = entry.older {
173                 starts = append(starts, entry.state)
174         }
175
176         slices.Reverse(starts)
177
178         return starts
179 }
180
181 // builtTree is a tree with each node built, with 1 byte of text.
182 type builtTree struct{}
183
184 func (builtTree) Node(level, index int) (Node, bool) {
185         return testNode(level, index, "a"), true
186 }
187
188 // partTree is a tree with the built nodes of its sizes.
189 type partTree struct {
190         sizes map[nodeKey]int
191 }
192
193 func (p partTree) Node(level, index int) (Node, bool) {
194         size, built := p.sizes[nodeKey{level, index}]
195         if !built {
196                 return Node{}, false
197         }
198
199         text := strings.Repeat("a", size)
200         if size == 7 {
201                 text = "aaa\naaa"
202         }
203
204         return testNode(level, index, text), true
205 }