peco.peco/issues_test.go
2026-02-20 18:42:24 +09:00

162 lines
4.9 KiB
Go

package peco
import (
"fmt"
"io"
"os"
"testing"
"time"
"context"
"github.com/peco/peco/config"
"github.com/peco/peco/filter"
"github.com/peco/peco/internal/keyseq"
"github.com/peco/peco/line"
"github.com/peco/peco/pipeline"
"github.com/stretchr/testify/require"
)
func TestIssue212_SanityCheck(t *testing.T) {
state, ctx := setupPecoTest(t)
// Check if the default layout type is honored */
// This the main issue on 212, but while we're at it, we're just
// going to check that all the default values are as expected
require.Equal(t, state.config.Layout, "top-down", "Default layout type should be 'top-down', got '%s'", state.config.Layout)
require.Equal(t, len(state.config.Keymap), 0, "Default keymap should be empty, but got '%#v'", state.config.Keymap)
defstyle := config.StyleSet{}
defstyle.Init()
require.Equal(t, state.config.Style, defstyle, "should be default style")
require.Equal(t, state.config.Prompt, "QUERY>", "Default prompt should be 'QUERY>', but got '%s'", state.config.Prompt)
// Okay, this time create a dummy config file, and read that in
f, err := os.CreateTemp(t.TempDir(), "peco-test-config")
require.NoError(t, err, "Failed to create temporary config file: %s", err)
fn := f.Name()
defer os.Remove(fn)
io.WriteString(f, `{
"Layout": "bottom-up"
}`)
f.Close()
state = newPeco()
go state.Run(ctx)
<-state.Ready()
require.NoError(t, state.config.ReadFilename(fn), "Failed to read config: %s", err)
require.Equal(t, state.config.Layout, "bottom-up", "Default layout type should be 'bottom-up', got '%s'", state.config.Layout)
}
func TestIssue345(t *testing.T) {
cfg, err := newConfig(`{
"Keymap": {
"C-t": "my.ToggleSelectionInAboveLine"
},
"Action": {
"my.ToggleSelectionInAboveLine": [
"peco.SelectUp",
"peco.ToggleSelectionAndSelectNext"
]
}
}`)
require.NoError(t, err, "newConfig should succeed")
defer os.Remove(cfg)
state := newPeco()
state.configReader = defaultConfigReader
require.NoError(t, state.config.Init(), "Config.Init should succeed")
state.Argv = append(state.Argv, []string{"--rcfile", cfg}...)
ctx, cancel := context.WithCancel(context.Background())
go state.Run(ctx)
defer cancel()
<-state.Ready()
ev := Event{
Type: EventKey,
Key: keyseq.KeyCtrlT,
}
require.NoError(t, state.Keymap().ExecuteAction(ctx, state, ev), "ExecuteAction should succeed")
// Brief pause to let async hub messages from the combined action
// be processed before context cancellation tears down goroutines.
time.Sleep(50 * time.Millisecond)
}
// TestIssue557_FilterBufSize verifies that configuring FilterBufSize allows
// FuzzyLongestSort to correctly sort results across what would otherwise be
// separate chunks. With the default buf size of 1000, lines are batched into
// 1000-line chunks and sorting only happens within each chunk. An exact match
// in chunk 2 would always appear after all chunk 1 results.
func TestIssue557_FilterBufSize(t *testing.T) {
const totalLines = 1100
const query = "exact"
// Build lines: 1 partial match, then 1098 non-matching lines, then 1 exact match.
// The exact match ("exact") is at index 1099, which with the default buf size of
// 1000 would land in the second chunk.
var lines []line.Line
lines = append(lines, line.NewRaw(0, "e_x_a_c_t filler text", false, false)) // fuzzy match
for i := 1; i < totalLines-1; i++ {
lines = append(lines, line.NewRaw(uint64(i), fmt.Sprintf("no match line %d", i), false, false))
}
lines = append(lines, line.NewRaw(uint64(totalLines-1), "exact", false, false)) // exact match, best result
f := filter.NewFuzzy(true)
// Use a configBufSize large enough to hold all lines in a single batch.
// This ensures the fuzzy filter sees all lines at once and can sort globally.
configBufSize := totalLines + 100
fp := newFilterProcessor(f, query, configBufSize, nil)
// Set up pipeline: source -> filterProcessor -> destination
p := pipeline.New()
src := &sliceSource{lines: lines}
p.SetSource(src)
p.Add(fp)
dst := NewMemoryBuffer(0)
p.SetDestination(dst)
ctx := f.NewContext(context.Background(), query)
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
err := p.Run(ctx)
require.NoError(t, err, "pipeline.Run should succeed")
require.True(t, dst.Size() >= 2, "expected at least 2 matched lines, got %d", dst.Size())
// The first result should be the exact match "exact" since it has the
// longest continuous match and shortest line length.
first, err := dst.LineAt(0)
require.NoError(t, err)
require.Equal(t, "exact", first.DisplayString(), "best match should be first")
}
// sliceSource is a simple pipeline.Source backed by a slice of lines.
type sliceSource struct {
lines []line.Line
}
func (s *sliceSource) Start(ctx context.Context, out pipeline.ChanOutput) {
defer close(out)
for _, l := range s.lines {
select {
case <-ctx.Done():
return
default:
out.Send(ctx, l)
}
}
}
func (s *sliceSource) Reset() {}