]> git.giacomolli.uk - onechat/blob - view_test.go
Add the view and save it across restarts
[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 TestRestoreViewRejectsAGap(t *testing.T) {
94 _, err := restoreView(viewState{
95 Lines: [][2]int{{0, 0}, {0, 2}},
96 }, chatHigh, chatLow, builtTree{})
97 require.ErrorContains(t, err, "does not start at message 1")
98 }
99
100 // pushEntry is one entry of Taelin's rollback state list, newest first. Under
101 // push alone, life stays 0, so the entry leaves it out.
102 type pushEntry struct {
103 keep bool
104 state int
105 older *pushEntry
106 }
107
108 // push is the push of rollback_state_list.js.
109 func push(state int, states *pushEntry) *pushEntry {
110 if states == nil {
111 return &pushEntry{
112 state: state,
113 }
114 }
115
116 if !states.keep {
117 return &pushEntry{
118 keep: true,
119 state: states.state,
120 older: states.older,
121 }
122 }
123
124 return &pushEntry{
125 state: state,
126 older: push(states.state, states.older),
127 }
128 }
129
130 // starts returns the states of the list, oldest first.
131 func (e *pushEntry) starts() []int {
132 var starts []int
133 for entry := e; entry != nil; entry = entry.older {
134 starts = append(starts, entry.state)
135 }
136
137 slices.Reverse(starts)
138
139 return starts
140 }
141
142 // builtTree is a tree with each node built, with 1 byte of text.
143 type builtTree struct{}
144
145 func (builtTree) Node(level, index int) (Node, bool) {
146 return testNode(level, index, "a"), true
147 }
148
149 // partTree is a tree with the built nodes of its sizes.
150 type partTree struct {
151 sizes map[nodeKey]int
152 }
153
154 func (p partTree) Node(level, index int) (Node, bool) {
155 size, built := p.sizes[nodeKey{level, index}]
156 if !built {
157 return Node{}, false
158 }
159
160 text := strings.Repeat("a", size)
161 if size == 7 {
162 text = "aaa\naaa"
163 }
164
165 return testNode(level, index, text), true
166 }