X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/9f17bfb1173524553fff5301b0664267b430e6e3..6e7c7f3d47f086b9d59fe40b91fb071e1528684e:/view_test.go diff --git a/view_test.go b/view_test.go index be38873..56abdd6 100644 --- a/view_test.go +++ b/view_test.go @@ -8,16 +8,17 @@ import ( "github.com/stretchr/testify/require" ) -func TestViewMergesInTheOrderOfTaelinsPush(t *testing.T) { - tree := builtTree{} +func TestViewMergesInTheOrderOfTheRollbackPush(t *testing.T) { + tree := ViewBuiltTree{} view := newView(0, 0) - var states *pushEntry + var states *ViewPushEntry for tick := range 20001 { - states = push(tick, states) + states = viewPush(tick, states) starts := states.starts() view.high = len(starts) view.low = len(starts) - view.Append(tree) + _, err := view.Append(tree) + require.NoError(t, err) lines := make([]int, len(view.lines)) for n, line := range view.lines { @@ -29,20 +30,21 @@ func TestViewMergesInTheOrderOfTaelinsPush(t *testing.T) { } func TestViewMergesInOneBatchFromHighToLow(t *testing.T) { - tree := builtTree{} + tree := ViewBuiltTree{} view := newView(10, 5) sizes := make([]int, 0, 20) for range 20 { - view.Append(tree) - sizes = append(sizes, view.Size(tree)) + _, err := view.Append(tree) + require.NoError(t, err) + sizes = append(sizes, view.Size()) } 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) } func TestViewMergesOnlyBuiltParents(t *testing.T) { - tree := partTree{ - sizes: map[nodeKey]int{ + tree := ViewPartTree{ + sizes: map[NodeKey]int{ {0, 0}: 1, {0, 1}: 1, {0, 2}: 1, @@ -54,81 +56,113 @@ func TestViewMergesOnlyBuiltParents(t *testing.T) { view := newView(3, 2) for range 4 { - view.Append(tree) + _, err := view.Append(tree) + require.NoError(t, err) } - require.Equal(t, []nodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, view.lines) + require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, view.lines) require.True(t, view.merging) - tree.sizes[nodeKey{1, 0}] = 1 - view.Append(tree) - require.Equal(t, []nodeKey{{1, 0}, {0, 2}, {0, 3}, {0, 4}}, view.lines) + tree.sizes[NodeKey{1, 0}] = 1 + _, err := view.Append(tree) + require.NoError(t, err) + require.Equal(t, []NodeKey{{1, 0}, {0, 2}, {0, 3}, {0, 4}}, view.lines) require.True(t, view.merging) - tree.sizes[nodeKey{1, 1}] = 1 - tree.sizes[nodeKey{1, 2}] = 1 - tree.sizes[nodeKey{2, 0}] = 1 - view.Append(tree) - require.Equal(t, []nodeKey{{2, 0}, {1, 2}}, view.lines) + tree.sizes[NodeKey{1, 1}] = 1 + tree.sizes[NodeKey{1, 2}] = 1 + tree.sizes[NodeKey{2, 0}] = 1 + _, err = view.Append(tree) + require.NoError(t, err) + require.Equal(t, []NodeKey{{2, 0}, {1, 2}}, view.lines) require.False(t, view.merging) } func TestViewRendersPendingLinesWithoutTheirMessage(t *testing.T) { - tree := partTree{ - sizes: map[nodeKey]int{ + tree := ViewPartTree{ + sizes: map[NodeKey]int{ {0, 0}: 3, {0, 1}: 3, {1, 0}: 7, }, } - view, err := restoreView(viewState{ + view, err := restoreView(ViewState{ Lines: [][2]int{{1, 0}, {0, 2}}, - }, chatHigh, chatLow, tree) + }, viewChatHigh, viewChatLow, tree) + require.NoError(t, err) + require.Equal(t, "\n0+2|aaa aaa\n2+1|"+viewPending+"\n", view.Render()) + require.Equal(t, 7+len(viewPending), view.Size()) +} + +func TestViewCoarsenTakesTheCoarserLines(t *testing.T) { + view, err := restoreView(ViewState{ + Lines: [][2]int{{1, 0}, {0, 2}, {0, 3}, {0, 4}, {0, 5}}, + }, 100, 100, ViewBuiltTree{}) + require.NoError(t, err) + + other, err := restoreView(ViewState{ + Lines: [][2]int{{0, 0}, {0, 1}, {1, 1}, {0, 4}, {0, 5}}, + }, 100, 100, ViewBuiltTree{}) + require.NoError(t, err) + + require.NoError(t, view.Coarsen(other, ViewBuiltTree{})) + require.Equal(t, []NodeKey{{1, 0}, {1, 1}, {0, 4}, {0, 5}}, view.lines) + + view.low = 2 + require.NoError(t, view.Coarsen(other, ViewBuiltTree{})) + require.Equal(t, []NodeKey{{2, 0}, {1, 2}}, view.lines) + require.False(t, view.merging) +} + +func TestViewContextStopsAtTheFirstPendingLine(t *testing.T) { + tree := ViewPartTree{ + sizes: map[NodeKey]int{ + {0, 0}: 1, + {0, 1}: 1, + {1, 0}: 1, + {0, 3}: 1, + }, + } + + view, err := restoreView(ViewState{ + Lines: [][2]int{{1, 0}, {0, 2}, {0, 3}}, + }, 100, 100, tree) require.NoError(t, err) - require.Equal(t, "\n0+2|aaa aaa\n2+1|"+pending+"\n", view.Render(tree)) - require.Equal(t, 7+len(pending), view.Size(tree)) + require.Equal(t, "\n", view.Context(0)) + require.Equal(t, "\n0+2|a\n", view.Context(2)) + require.Equal(t, "\n0+2|a\n", view.Context(3)) } func TestRestoreViewRejectsAGap(t *testing.T) { - _, err := restoreView(viewState{ + _, err := restoreView(ViewState{ Lines: [][2]int{{0, 0}, {0, 2}}, - }, chatHigh, chatLow, builtTree{}) + }, viewChatHigh, viewChatLow, ViewBuiltTree{}) require.ErrorContains(t, err, "does not start at message 1") } -// pushEntry is one entry of Taelin's rollback state list, newest first. Under -// push alone, life stays 0, so the entry leaves it out. -type pushEntry struct { - keep bool - state int - older *pushEntry -} +// ViewBuiltTree is a tree with each node built, with 1 byte of text. +type ViewBuiltTree struct{} -// push is the push of rollback_state_list.js. -func push(state int, states *pushEntry) *pushEntry { - if states == nil { - return &pushEntry{ - state: state, - } - } +func (ViewBuiltTree) Built(level, index int) (bool, error) { + return true, nil +} - if !states.keep { - return &pushEntry{ - keep: true, - state: states.state, - older: states.older, - } - } +func (ViewBuiltTree) Node(level, index int) (Node, bool, error) { + return treeTestNode(level, index, "a"), true, nil +} - return &pushEntry{ - state: state, - older: push(states.state, states.older), - } +// ViewPushEntry is one entry of the rollback state list of the reference +// design, newest first. Under push alone, life stays 0, so the entry leaves it +// out. +type ViewPushEntry struct { + keep bool + state int + older *ViewPushEntry } // starts returns the states of the list, oldest first. -func (e *pushEntry) starts() []int { +func (e *ViewPushEntry) starts() []int { var starts []int for entry := e; entry != nil; entry = entry.older { starts = append(starts, entry.state) @@ -139,22 +173,20 @@ func (e *pushEntry) starts() []int { return starts } -// builtTree is a tree with each node built, with 1 byte of text. -type builtTree struct{} - -func (builtTree) Node(level, index int) (Node, bool) { - return testNode(level, index, "a"), true +// ViewPartTree is a tree with the built nodes of its sizes. +type ViewPartTree struct { + sizes map[NodeKey]int } -// partTree is a tree with the built nodes of its sizes. -type partTree struct { - sizes map[nodeKey]int +func (p ViewPartTree) Built(level, index int) (bool, error) { + _, built := p.sizes[NodeKey{level, index}] + return built, nil } -func (p partTree) Node(level, index int) (Node, bool) { - size, built := p.sizes[nodeKey{level, index}] +func (p ViewPartTree) Node(level, index int) (Node, bool, error) { + size, built := p.sizes[NodeKey{level, index}] if !built { - return Node{}, false + return Node{}, false, nil } text := strings.Repeat("a", size) @@ -162,5 +194,31 @@ func (p partTree) Node(level, index int) (Node, bool) { text = "aaa\naaa" } - return testNode(level, index, text), true + return treeTestNode(level, index, text), true, nil +} + +// viewPush adds state to the newest end of states and returns the new list. An +// empty list gets one entry. When the newest entry is not kept, it becomes kept +// with its own state, and state is dropped. When it is kept, state becomes a +// new newest entry, and the state of the old newest entry goes to the older +// entries by the same push. +func viewPush(state int, states *ViewPushEntry) *ViewPushEntry { + if states == nil { + return &ViewPushEntry{ + state: state, + } + } + + if !states.keep { + return &ViewPushEntry{ + keep: true, + state: states.state, + older: states.older, + } + } + + return &ViewPushEntry{ + state: state, + older: viewPush(states.state, states.older), + } }