src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/modes/location_test.go (about)

     1  package modes
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"src.elv.sh/pkg/cli"
    13  	. "src.elv.sh/pkg/cli/clitest"
    14  	"src.elv.sh/pkg/cli/term"
    15  	"src.elv.sh/pkg/store/storedefs"
    16  	"src.elv.sh/pkg/testutil"
    17  	"src.elv.sh/pkg/ui"
    18  )
    19  
    20  type locationStore struct {
    21  	storedDirs []storedefs.Dir
    22  	dirsError  error
    23  	chdir      func(dir string) error
    24  	wd         string
    25  }
    26  
    27  func (ts locationStore) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) {
    28  	dirs := []storedefs.Dir{}
    29  	for _, dir := range ts.storedDirs {
    30  		if _, ok := blacklist[dir.Path]; ok {
    31  			continue
    32  		}
    33  		dirs = append(dirs, dir)
    34  	}
    35  	return dirs, ts.dirsError
    36  }
    37  
    38  func (ts locationStore) Chdir(dir string) error {
    39  	if ts.chdir == nil {
    40  		return nil
    41  	}
    42  	return ts.chdir(dir)
    43  }
    44  
    45  func (ts locationStore) Getwd() (string, error) {
    46  	return ts.wd, nil
    47  }
    48  
    49  func TestNewLocation_NoStore(t *testing.T) {
    50  	f := Setup()
    51  	defer f.Stop()
    52  
    53  	_, err := NewLocation(f.App, LocationSpec{})
    54  	if err != errNoDirectoryHistoryStore {
    55  		t.Error("want errNoDirectoryHistoryStore")
    56  	}
    57  }
    58  
    59  func TestNewLocation_StoreError(t *testing.T) {
    60  	f := Setup()
    61  	defer f.Stop()
    62  
    63  	_, err := NewLocation(f.App,
    64  		LocationSpec{Store: locationStore{dirsError: errors.New("ERROR")}})
    65  	if err.Error() != "db error: ERROR" {
    66  		t.Error("want db error")
    67  	}
    68  }
    69  
    70  func TestLocation_FullWorkflow(t *testing.T) {
    71  	home := testutil.InTempHome(t)
    72  	f := Setup()
    73  	defer f.Stop()
    74  
    75  	errChdir := errors.New("mock chdir error")
    76  	chdirCh := make(chan string, 100)
    77  	dirs := []storedefs.Dir{
    78  		{Path: filepath.Join(home, "go"), Score: 200},
    79  		{Path: home, Score: 100},
    80  		{Path: fixPath("/tmp/foo/bar/lorem/ipsum"), Score: 50},
    81  	}
    82  	startLocation(f.App, LocationSpec{Store: locationStore{
    83  		storedDirs: dirs,
    84  		chdir:      func(dir string) error { chdirCh <- dir; return errChdir },
    85  	}})
    86  
    87  	// Test UI.
    88  	wantBuf := locationBuf(
    89  		"",
    90  		"200 "+filepath.Join("~", "go"),
    91  		"100 ~",
    92  		" 50 "+fixPath("/tmp/foo/bar/lorem/ipsum"))
    93  	f.TTY.TestBuffer(t, wantBuf)
    94  
    95  	// Test filtering.
    96  	f.TTY.Inject(term.K('f'), term.K('o'))
    97  
    98  	wantBuf = locationBuf(
    99  		"fo",
   100  		" 50 "+fixPath("/tmp/foo/bar/lorem/ipsum"))
   101  	f.TTY.TestBuffer(t, wantBuf)
   102  
   103  	// Test accepting.
   104  	f.TTY.Inject(term.K(ui.Enter))
   105  	// There should be no change to codearea after accepting.
   106  	f.TestTTY(t /* nothing */)
   107  	// Error from Chdir should be sent to notes.
   108  	f.TestTTYNotes(t,
   109  		"error: mock chdir error", Styles,
   110  		"!!!!!!")
   111  	// Chdir should be called.
   112  	wantChdir := fixPath("/tmp/foo/bar/lorem/ipsum")
   113  	select {
   114  	case got := <-chdirCh:
   115  		if got != wantChdir {
   116  			t.Errorf("Chdir called with %s, want %s", got, wantChdir)
   117  		}
   118  	case <-time.After(testutil.Scaled(time.Second)):
   119  		t.Errorf("Chdir not called")
   120  	}
   121  }
   122  
   123  func TestLocation_Hidden(t *testing.T) {
   124  	f := Setup()
   125  	defer f.Stop()
   126  
   127  	dirs := []storedefs.Dir{
   128  		{Path: fixPath("/usr/bin"), Score: 200},
   129  		{Path: fixPath("/usr"), Score: 100},
   130  		{Path: fixPath("/tmp"), Score: 50},
   131  	}
   132  	startLocation(f.App, LocationSpec{
   133  		Store:         locationStore{storedDirs: dirs},
   134  		IterateHidden: func(f func(string)) { f(fixPath("/usr")) },
   135  	})
   136  	// Test UI.
   137  	wantBuf := locationBuf(
   138  		"",
   139  		"200 "+fixPath("/usr/bin"),
   140  		" 50 "+fixPath("/tmp"))
   141  	f.TTY.TestBuffer(t, wantBuf)
   142  }
   143  
   144  func TestLocation_Pinned(t *testing.T) {
   145  	f := Setup()
   146  	defer f.Stop()
   147  
   148  	dirs := []storedefs.Dir{
   149  		{Path: fixPath("/usr/bin"), Score: 200},
   150  		{Path: fixPath("/usr"), Score: 100},
   151  		{Path: fixPath("/tmp"), Score: 50},
   152  	}
   153  	startLocation(f.App, LocationSpec{
   154  		Store:         locationStore{storedDirs: dirs},
   155  		IteratePinned: func(f func(string)) { f(fixPath("/home")); f(fixPath("/usr")) },
   156  	})
   157  	// Test UI.
   158  	wantBuf := locationBuf(
   159  		"",
   160  		"  * "+fixPath("/home"),
   161  		"  * "+fixPath("/usr"),
   162  		"200 "+fixPath("/usr/bin"),
   163  		" 50 "+fixPath("/tmp"))
   164  	f.TTY.TestBuffer(t, wantBuf)
   165  }
   166  
   167  func TestLocation_HideWd(t *testing.T) {
   168  	f := Setup()
   169  	defer f.Stop()
   170  
   171  	dirs := []storedefs.Dir{
   172  		{Path: fixPath("/home"), Score: 200},
   173  		{Path: fixPath("/tmp"), Score: 50},
   174  	}
   175  	startLocation(f.App, LocationSpec{Store: locationStore{storedDirs: dirs, wd: fixPath("/home")}})
   176  	// Test UI.
   177  	wantBuf := locationBuf(
   178  		"",
   179  		" 50 "+fixPath("/tmp"))
   180  	f.TTY.TestBuffer(t, wantBuf)
   181  }
   182  
   183  func TestLocation_Workspace(t *testing.T) {
   184  	f := Setup()
   185  	defer f.Stop()
   186  
   187  	chdir := ""
   188  	dirs := []storedefs.Dir{
   189  		{Path: fixPath("home/src"), Score: 200},
   190  		{Path: fixPath("ws1/src"), Score: 150},
   191  		{Path: fixPath("ws2/bin"), Score: 100},
   192  		{Path: fixPath("/tmp"), Score: 50},
   193  	}
   194  	startLocation(f.App, LocationSpec{
   195  		Store: locationStore{
   196  			storedDirs: dirs,
   197  			wd:         fixPath("/home/elf/bin"),
   198  			chdir: func(dir string) error {
   199  				chdir = dir
   200  				return nil
   201  			},
   202  		},
   203  		IterateWorkspaces: func(f func(kind, pattern string) bool) {
   204  			if runtime.GOOS == "windows" {
   205  				// Invalid patterns are ignored.
   206  				f("ws1", `C:\\usr\\[^\\+`)
   207  				f("home", `C:\\home\\[^\\]+`)
   208  				f("ws2", `C:\\tmp\[^\]+`)
   209  			} else {
   210  				// Invalid patterns are ignored.
   211  				f("ws1", "/usr/[^/+")
   212  				f("home", "/home/[^/]+")
   213  				f("ws2", "/tmp/[^/]+")
   214  			}
   215  		},
   216  	})
   217  
   218  	wantBuf := locationBuf(
   219  		"",
   220  		"200 "+fixPath("home/src"),
   221  		" 50 "+fixPath("/tmp"))
   222  	f.TTY.TestBuffer(t, wantBuf)
   223  
   224  	f.TTY.Inject(term.K(ui.Enter))
   225  	f.TestTTY(t /* nothing */)
   226  	wantChdir := fixPath("/home/elf/src")
   227  	if chdir != wantChdir {
   228  		t.Errorf("got chdir %q, want %q", chdir, wantChdir)
   229  	}
   230  }
   231  
   232  func locationBuf(filter string, lines ...string) *term.Buffer {
   233  	b := term.NewBufferBuilder(50).
   234  		Newline(). // empty code area
   235  		WriteStyled(modeLine(" LOCATION ", true)).
   236  		Write(filter).SetDotHere()
   237  	for i, line := range lines {
   238  		b.Newline()
   239  		if i == 0 {
   240  			b.WriteStyled(ui.T(fmt.Sprintf("%-50s", line), ui.Inverse))
   241  		} else {
   242  			b.Write(line)
   243  		}
   244  	}
   245  	return b.Buffer()
   246  }
   247  
   248  func fixPath(path string) string {
   249  	if runtime.GOOS != "windows" {
   250  		return path
   251  	}
   252  	if path[0] == '/' {
   253  		path = "C:" + path
   254  	}
   255  	return strings.ReplaceAll(path, "/", "\\")
   256  }
   257  
   258  func startLocation(app cli.App, spec LocationSpec) {
   259  	w, err := NewLocation(app, spec)
   260  	startMode(app, w, err)
   261  }