From 0ccff01ddd59a275db77e7e90b2c381e506aec46 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 20 Feb 2026 20:37:44 +0900 Subject: [PATCH] increase test coverage --- action_test.go | 45 +++++++++++++++++ internal/keyseq/ternary_test.go | 68 +++++++++++-------------- page_test.go | 73 +++++++++++++++++++++++++++ selection/selection_test.go | 88 +++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 40 deletions(-) create mode 100644 page_test.go diff --git a/action_test.go b/action_test.go index 1e84399..2f5f35b 100644 --- a/action_test.go +++ b/action_test.go @@ -766,6 +766,51 @@ func TestGHIssue428_PgUpPgDnDefaultBindings(t *testing.T) { }) } +func TestDefaultKeyBindings(t *testing.T) { + ctx := context.Background() + rHub := &recordingHub{} + + state := New() + state.hub = rHub + state.selection = selection.New() + state.currentLineBuffer = NewMemoryBuffer(0) + + state.config.Keymap = map[string]string{} + state.config.Action = map[string][]string{} + require.NoError(t, state.populateKeymap(), "populateKeymap should succeed") + + km := state.Keymap() + + tests := []struct { + name string + key keyseq.KeyType + expected hub.PagingRequest + }{ + {"ArrowUp triggers SelectUp", keyseq.KeyArrowUp, hub.ToLineAbove}, + {"ArrowDown triggers SelectDown", keyseq.KeyArrowDown, hub.ToLineBelow}, + {"ArrowRight triggers ScrollPageDown", keyseq.KeyArrowRight, hub.ToScrollPageDown}, + {"ArrowLeft triggers ScrollPageUp", keyseq.KeyArrowLeft, hub.ToScrollPageUp}, + {"PgDn triggers ScrollPageDown", keyseq.KeyPgdn, hub.ToScrollPageDown}, + {"PgUp triggers ScrollPageUp", keyseq.KeyPgup, hub.ToScrollPageUp}, + {"Home triggers ScrollFirstItem", keyseq.KeyHome, hub.ToScrollFirstItem}, + {"End triggers ScrollLastItem", keyseq.KeyEnd, hub.ToScrollLastItem}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rHub.reset() + + ev := Event{Key: tt.key} + err := km.ExecuteAction(ctx, state, ev) + require.NoError(t, err, "%s should resolve to an action", tt.name) + + pagingArgs := rHub.getPagingArgs() + require.Len(t, pagingArgs, 1, "expected one paging call") + require.Equal(t, tt.expected, pagingArgs[0]) + }) + } +} + // TestGHIssue455_RefreshScreenSendsForceSync verifies that doRefreshScreen // sends DrawOptions with both DisableCache and ForceSync set to true. func TestGHIssue455_RefreshScreenSendsForceSync(t *testing.T) { diff --git a/internal/keyseq/ternary_test.go b/internal/keyseq/ternary_test.go index b8dc1d1..b0d32a7 100644 --- a/internal/keyseq/ternary_test.go +++ b/internal/keyseq/ternary_test.go @@ -20,44 +20,32 @@ func TestBalance(t *testing.T) { require.Equal(t, 15, trie.Size()) trie.Balance() - /* - n8 := trie.Root().(*TernaryNode).firstChild - checkTrieNode(t, n8, '8', 7) - n4 := n8.low - checkTrieNode(t, n4, '4', 3) - n12 := n8.high - checkTrieNode(t, n12, 'C', 11) - n2 := n4.low - checkTrieNode(t, n2, '2', 1) - n6 := n4.high - checkTrieNode(t, n6, '6', 5) - n10 := n12.low - checkTrieNode(t, n10, 'A', 9) - n14 := n12.high - checkTrieNode(t, n14, 'E', 13) - n1 := n2.low - checkTrieNode(t, n1, '1', 0) - n3 := n2.high - checkTrieNode(t, n3, '3', 2) - n5 := n6.low - checkTrieNode(t, n5, '5', 4) - n7 := n6.high - checkTrieNode(t, n7, '7', 6) - n9 := n10.low - checkTrieNode(t, n9, '9', 8) - n11 := n10.high - checkTrieNode(t, n11, 'B', 10) - n13 := n14.low - checkTrieNode(t, n13, 'D', 12) - n15 := n14.high - checkTrieNode(t, n15, 'F', 14) - assertNilBoth(t, n1) - assertNilBoth(t, n3) - assertNilBoth(t, n5) - assertNilBoth(t, n7) - assertNilBoth(t, n9) - assertNilBoth(t, n11) - assertNilBoth(t, n13) - assertNilBoth(t, n15) - */ + // After balancing, all keys must still be retrievable with correct values. + for i, k := range list { + node := trie.Get(k) + require.NotNil(t, node, "key %d should be found after Balance", i) + require.Equal(t, i, node.Value(), "value for key %d should be %d", i, i) + } + + // Size must be unchanged after balancing. + require.Equal(t, 15, trie.Size()) +} + +func TestBalancePreservesMultiKeySequences(t *testing.T) { + trie := NewTernaryTrie() + + // Insert multi-key sequences + k1 := KeyList{{0, 0, 'a'}, {0, 0, 'b'}} + k2 := KeyList{{0, 0, 'a'}, {0, 0, 'c'}} + k3 := KeyList{{0, 0, 'x'}} + + trie.Put(k1, "ab") + trie.Put(k2, "ac") + trie.Put(k3, "x") + + trie.Balance() + + require.Equal(t, "ab", trie.GetList(k1).Value()) + require.Equal(t, "ac", trie.GetList(k2).Value()) + require.Equal(t, "x", trie.GetList(k3).Value()) } diff --git a/page_test.go b/page_test.go new file mode 100644 index 0000000..cbc7879 --- /dev/null +++ b/page_test.go @@ -0,0 +1,73 @@ +package peco + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLocationSettersAndGetters(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + set func(*Location, int) + get func(*Location) int + values []int + }{ + {"Column", (*Location).SetColumn, (*Location).Column, []int{0, 5, 100}}, + {"LineNumber", (*Location).SetLineNumber, (*Location).LineNumber, []int{0, 10, 999}}, + {"Offset", (*Location).SetOffset, (*Location).Offset, []int{0, 3, 42}}, + {"PerPage", (*Location).SetPerPage, (*Location).PerPage, []int{0, 20, 50}}, + {"Page", (*Location).SetPage, (*Location).Page, []int{0, 1, 7}}, + {"Total", (*Location).SetTotal, (*Location).Total, []int{0, 100, 5000}}, + {"MaxPage", (*Location).SetMaxPage, (*Location).MaxPage, []int{0, 5, 25}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var loc Location + for _, v := range tt.values { + tt.set(&loc, v) + require.Equal(t, v, tt.get(&loc)) + } + }) + } +} + +func TestLocationConcurrentAccess(t *testing.T) { + t.Parallel() + var loc Location + + var wg sync.WaitGroup + for i := range 100 { + wg.Add(2) + go func(n int) { + defer wg.Done() + loc.SetLineNumber(n) + }(i) + go func() { + defer wg.Done() + _ = loc.LineNumber() + }() + } + wg.Wait() +} + +func TestLocationPageCropSnapshot(t *testing.T) { + t.Parallel() + var loc Location + loc.SetPerPage(20) + loc.SetPage(3) + + crop := loc.PageCrop() + + // Mutating the Location after snapshot should not affect the crop + loc.SetPerPage(50) + loc.SetPage(0) + + require.Equal(t, 20, crop.perPage) + require.Equal(t, 3, crop.currentPage) +} diff --git a/selection/selection_test.go b/selection/selection_test.go index e7b8b7c..7b17319 100644 --- a/selection/selection_test.go +++ b/selection/selection_test.go @@ -1,6 +1,7 @@ package selection import ( + "sync" "testing" "github.com/peco/peco/line" @@ -22,3 +23,90 @@ func TestSelection(t *testing.T) { s.Remove(alice) require.Equal(t, 1, s.Len()) } + +func TestSelectionHas(t *testing.T) { + t.Parallel() + s := New() + alice := line.NewRaw(0, "Alice", false, false) + bob := line.NewRaw(1, "Bob", false, false) + + s.Add(alice) + require.True(t, s.Has(alice)) + require.False(t, s.Has(bob)) +} + +func TestSelectionAscendOrder(t *testing.T) { + t.Parallel() + s := New() + s.Add(line.NewRaw(3, "Charlie", false, false)) + s.Add(line.NewRaw(1, "Alice", false, false)) + s.Add(line.NewRaw(2, "Bob", false, false)) + + var ids []uint64 + s.Ascend(func(l line.Line) bool { + ids = append(ids, l.ID()) + return true + }) + + require.Equal(t, []uint64{1, 2, 3}, ids, "Ascend should iterate in ID order") +} + +func TestSelectionReset(t *testing.T) { + t.Parallel() + s := New() + s.Add(line.NewRaw(0, "Alice", false, false)) + s.Add(line.NewRaw(1, "Bob", false, false)) + require.Equal(t, 2, s.Len()) + + s.Reset() + require.Equal(t, 0, s.Len()) +} + +func TestSelectionCopy(t *testing.T) { + t.Parallel() + src := New() + src.Add(line.NewRaw(0, "Alice", false, false)) + src.Add(line.NewRaw(1, "Bob", false, false)) + + dst := New() + src.Copy(dst) + + require.Equal(t, 2, dst.Len()) + require.True(t, dst.Has(line.NewRaw(0, "Alice", false, false))) + require.True(t, dst.Has(line.NewRaw(1, "Bob", false, false))) +} + +func TestSelectionConcurrentAccess(t *testing.T) { + t.Parallel() + s := New() + + var wg sync.WaitGroup + for i := range 50 { + wg.Add(2) + go func(id uint64) { + defer wg.Done() + s.Add(line.NewRaw(id, "line", false, false)) + }(uint64(i)) + go func(id uint64) { + defer wg.Done() + s.Has(line.NewRaw(id, "line", false, false)) + }(uint64(i)) + } + wg.Wait() + + require.Equal(t, 50, s.Len()) +} + +func TestRangeStart(t *testing.T) { + t.Parallel() + var rs RangeStart + + require.False(t, rs.Valid()) + + rs.SetValue(5) + require.True(t, rs.Valid()) + require.Equal(t, 5, rs.Value()) + + rs.Reset() + require.False(t, rs.Valid()) +}