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