package preview import ( "errors" "os/exec" "strings" "testing" ) func TestParseMode(t *testing.T) { cases := map[string]Mode{ "": ModeAuto, "auto": ModeAuto, "AUTO": ModeAuto, "on": ModeOn, " on ": ModeOn, "off": ModeOff, } for in, want := range cases { got, err := ParseMode(in) if err != nil { t.Errorf("ParseMode(%q) err = %v", in, err) continue } if got != want { t.Errorf("ParseMode(%q) = %q, want %q", in, got, want) } } if _, err := ParseMode("nope"); err == nil { t.Errorf("ParseMode(nope) should have errored") } } func TestResolve(t *testing.T) { type tc struct { mode Mode inTmux bool stdoutTTY bool want bool wantErr error } cases := map[string]tc{ "off-anywhere": {ModeOff, false, false, false, nil}, "off-in-tmux-tty": {ModeOff, true, true, false, nil}, "on-in-tmux": {ModeOn, true, false, true, nil}, "on-outside-tmux-errs": {ModeOn, false, true, false, ErrNoTmuxForced}, "auto-no-tmux": {ModeAuto, false, true, false, nil}, "auto-tmux-no-tty": {ModeAuto, true, false, false, nil}, "auto-tmux-and-tty": {ModeAuto, true, true, true, nil}, } for name, c := range cases { t.Run(name, func(t *testing.T) { d, err := Resolve(c.mode, c.inTmux, c.stdoutTTY) if c.wantErr != nil { if !errors.Is(err, c.wantErr) { t.Fatalf("err = %v, want %v", err, c.wantErr) } return } if err != nil { t.Fatalf("err = %v", err) } if d.ShouldPreview != c.want { t.Errorf("ShouldPreview = %v, want %v (reason: %s)", d.ShouldPreview, c.want, d.Reason) } }) } } func TestSpawn_BuildsCorrectCommand(t *testing.T) { var captured *exec.Cmd s := &Spawner{ LookPath: func(name string) (string, error) { switch name { case "tmux": return "/usr/bin/tmux", nil case "tmux-img": return "/home/m/.local/bin/tmux-img", nil } return "", exec.ErrNotFound }, Run: func(c *exec.Cmd) error { captured = c return nil }, } if err := s.Spawn("/tmp/imagen/cat.png", "cat-in-a-fishbowl"); err != nil { t.Fatalf("Spawn: %v", err) } if captured == nil { t.Fatal("Run was not called") } if captured.Path != "/usr/bin/tmux" { t.Errorf("Path = %q, want /usr/bin/tmux", captured.Path) } args := captured.Args if len(args) < 6 { t.Fatalf("args = %v (need at least 6)", args) } // tmux new-window -d -n img: '' if args[1] != "new-window" { t.Errorf("args[1] = %q, want new-window", args[1]) } if args[2] != "-d" { t.Errorf("args[2] = %q, want -d", args[2]) } if args[3] != "-n" { t.Errorf("args[3] = %q, want -n", args[3]) } if args[4] != "img:cat-in-a-fishbowl" { t.Errorf("args[4] = %q, want img:cat-in-a-fishbowl", args[4]) } shellCmd := args[5] if !strings.Contains(shellCmd, "tmux-img") || !strings.Contains(shellCmd, "--hold") || !strings.Contains(shellCmd, "/tmp/imagen/cat.png") { t.Errorf("shell cmd %q missing expected pieces", shellCmd) } } func TestSpawn_PathWithSpacesAndQuotes(t *testing.T) { var captured *exec.Cmd s := &Spawner{ LookPath: func(name string) (string, error) { if name == "tmux" { return "/usr/bin/tmux", nil } if name == "tmux-img" { return "/usr/local/bin/tmux-img", nil } return "", exec.ErrNotFound }, Run: func(c *exec.Cmd) error { captured = c; return nil }, } weird := "/tmp/imagen/o'malley's cat.png" if err := s.Spawn(weird, "slug"); err != nil { t.Fatalf("Spawn: %v", err) } shellCmd := captured.Args[5] // Single-quoted with the embedded apostrophe escaped via the // '\'' shell idiom — confirm we did not just splice the raw path. if strings.Contains(shellCmd, "o'malley's") { t.Errorf("shell cmd %q contains unescaped apostrophes", shellCmd) } } func TestSpawn_MissingTmux(t *testing.T) { s := &Spawner{ LookPath: func(string) (string, error) { return "", exec.ErrNotFound }, Run: func(*exec.Cmd) error { return nil }, } err := s.Spawn("/x.png", "s") if !errors.Is(err, ErrTmuxMissing) { t.Errorf("err = %v, want ErrTmuxMissing", err) } } func TestSpawn_MissingTmuxImg(t *testing.T) { s := &Spawner{ LookPath: func(name string) (string, error) { if name == "tmux" { return "/usr/bin/tmux", nil } return "", exec.ErrNotFound }, Run: func(*exec.Cmd) error { return nil }, } err := s.Spawn("/x.png", "s") if !errors.Is(err, ErrTmuxImgMissing) { t.Errorf("err = %v, want ErrTmuxImgMissing", err) } }