Repository: rsteube/carapace
Pull request #700 (2023-01-22 11:11:16)

sandbox: added preinvoke test


Patch:
diff --git a/action.go b/action.go
index c54c0e3..09b74d7 100644
--- a/action.go
+++ b/action.go
@@ -101,7 +101,7 @@ func (a Action) UsageF(f func() string) Action {
 	})
 }
 
-// Style sets the style
+// Style sets the style.
 //
 //	ActionValues("yes").Style(style.Green)
 //	ActionValues("no").Style(style.Red)
@@ -111,7 +111,7 @@ func (a Action) Style(s string) Action {
 	})
 }
 
-// Style sets the style using a reference
+// Style sets the style using a reference.
 //
 //	ActionValues("value").StyleR(&style.Carapace.Value)
 //	ActionValues("description").StyleR(&style.Carapace.Value)
@@ -124,7 +124,7 @@ func (a Action) StyleR(s *string) Action {
 	})
 }
 
-// Style sets the style using a function
+// Style sets the style using a function.
 //
 //	ActionValues("dir/", "test.txt").StyleF(style.ForPathExt)
 //	ActionValues("true", "false").StyleF(style.ForKeyword)
@@ -162,7 +162,7 @@ func (a Action) TagF(f func(value string) string) Action {
 	})
 }
 
-// Chdir changes the current working directory to the named directory during invocation.
+// Chdir changes the current working directory to the named directory for the duration of invocation.
 func (a Action) Chdir(dir string) Action {
 	return ActionCallback(func(c Context) Action {
 		abs, err := c.Abs(dir)
@@ -211,7 +211,7 @@ func (a Action) UniqueList(divider string) Action {
 	})
 }
 
-// Prefix adds a prefix to values (only the ones inserted, not the display values)
+// Prefix adds a prefix to values (only the ones inserted, not the display values).
 //
 //	carapace.ActionValues("melon", "drop", "fall").Prefix("water")
 func (a Action) Prefix(prefix string) Action {
@@ -220,7 +220,7 @@ func (a Action) Prefix(prefix string) Action {
 	})
 }
 
-// Suffix adds a suffx to values (only the ones inserted, not the display values)
+// Suffix adds a suffx to values (only the ones inserted, not the display values).
 //
 //	carapace.ActionValues("apple", "melon", "orange").Suffix("juice")
 func (a Action) Suffix(suffix string) Action {
diff --git a/pkg/sandbox/sandbox.go b/pkg/sandbox/sandbox.go
index 3cf56ed..e9798f2 100644
--- a/pkg/sandbox/sandbox.go
+++ b/pkg/sandbox/sandbox.go
@@ -40,6 +40,7 @@ func newSandbox(t *testing.T, f func() *cobra.Command) Sandbox {
 	}
 }
 
+// Keep prevents removal of the sandbox directory.
 func (s *Sandbox) Keep() {
 	s.keep = true
 }
@@ -54,6 +55,12 @@ func (s *Sandbox) remove() {
 	}
 }
 
+// Files creates files within the sandbox directory.
+//
+//	s.Files(
+//		"file1.txt", "content of files1.txt",
+//		"dir1/file2.md", "content of file2.md",
+//	)
 func (s *Sandbox) Files(args ...string) {
 	if len(args)%2 != 0 {
 		s.t.Errorf("invalid amount of arguments: %v", len(args))
@@ -100,11 +107,14 @@ func (r reply) With(s string) {
 	r.mock.Replies[r.call] = s
 }
 
+// NewContext creates a new context enriched with sandbox specifics.
 func (s *Sandbox) NewContext(args ...string) carapace.Context {
 	context := carapace.NewContext(args...)
 	for key, value := range s.env {
 		context.Setenv(key, value)
 	}
+	context.Dir = s.mock.Dir
+	// TODO set mockedreplies in context
 	return context
 }
 
@@ -155,7 +165,7 @@ func (r run) invoke(a carapace.Action) string {
 func (r run) Expect(expected carapace.Action) {
 	r.t.Run(r.id, func(t *testing.T) {
 		// t.Parallel() TODO prevent concurrent map write for this (storage.go)
-		assert.Equal(r.t, r.invoke(expected.Chdir(r.dir)), r.invoke(r.actual))
+		assert.Equal(r.t, r.invoke(expected), r.invoke(r.actual))
 	})
 }
 
@@ -179,9 +189,14 @@ func Package(t *testing.T, pkg string) (f func(func(s *Sandbox))) {
 				args := []string{"run", pkg, "_carapace", "export", ""}
 				args = append(args, c.Args...)
 				args = append(args, c.CallbackValue)
+
+				var err error
+				if c.Dir, err = os.Getwd(); err != nil { // `go run` needs to run in actual workdir and not the sandbox dir
+					return carapace.ActionMessage(err.Error())
+				}
 				return carapace.ActionExecCommand("go", args...)(func(output []byte) carapace.Action {
 					return carapace.ActionImport(output)
-				})
+				}).Invoke(c).ToA()
 			}),
 		)
 		return cmd
diff --git a/storage.go b/storage.go
index 9ccd3b6..a6e5c32 100644
--- a/storage.go
+++ b/storage.go
@@ -56,41 +56,36 @@ func (s _storage) preRun(cmd *cobra.Command, args []string) {
 }
 
 func (s _storage) preinvoke(cmd *cobra.Command, flag *pflag.Flag, action Action) Action {
-	// TODO yuck - clean this up
-	entry := s.get(cmd)
 	a := action
-	if entry.preinvoke != nil {
+	if entry := s.get(cmd); entry.preinvoke != nil {
 		a = ActionCallback(func(c Context) Action {
 			return entry.preinvoke(cmd, flag, action)
 		})
 	}
+
 	if cmd.HasParent() {
-		// TODO from cmd passed to preinvoke function
 		return s.preinvoke(cmd.Parent(), flag, a)
 	}
 	return a
 }
 
-func (s _storage) getPositional(cmd *cobra.Command, pos int) Action {
+func (s _storage) getPositional(cmd *cobra.Command, index int) Action {
 	entry := s.get(cmd)
+	isDash := common.IsDash(cmd)
 
 	var a Action
-	// TODO nil check?
-	if !common.IsDash(cmd) {
-		if len(entry.positional) > pos {
-			a = s.preinvoke(cmd, nil, entry.positional[pos])
-		} else {
-			a = s.preinvoke(cmd, nil, entry.positionalAny)
-		}
-	} else {
-		if len(entry.dash) > pos {
-			a = s.preinvoke(cmd, nil, entry.dash[pos])
-		} else {
-			a = s.preinvoke(cmd, nil, entry.dashAny)
-		}
+	switch {
+	case !isDash && len(entry.positional) > index:
+		a = s.preinvoke(cmd, nil, entry.positional[index])
+	case !isDash:
+		a = s.preinvoke(cmd, nil, entry.positionalAny)
+	case len(entry.dash) > index:
+		a = s.preinvoke(cmd, nil, entry.dash[index])
+	default:
+		a = s.preinvoke(cmd, nil, entry.dashAny)
 	}
 
-	return ActionCallback(func(c Context) Action { // TODO verify order of execution is correct
+	return ActionCallback(func(c Context) Action {
 		invoked := a.Invoke(c)
 		if invoked.meta.Usage == "" && len(strings.Fields(cmd.Use)) > 1 {
 			invoked.meta.Usage = cmd.Use
diff --git a/traverse.go b/traverse.go
index 299be8e..a8544e5 100644
--- a/traverse.go
+++ b/traverse.go
@@ -91,7 +91,6 @@ loop:
 				context.Args = c.Flags().Args()
 			}
 
-			// TODO what if there is no next argument
 			return traverse(subcommand(c, arg), args[i+1:])
 
 		// positional


Test patch:
diff --git a/pkg/sandbox/sandbox_test.go b/pkg/sandbox/sandbox_test.go
index 7e6112a..bcd9f67 100644
--- a/pkg/sandbox/sandbox_test.go
+++ b/pkg/sandbox/sandbox_test.go
@@ -1,13 +1,63 @@
 package sandbox
 
 import (
+	"os"
 	"testing"
 
 	"github.com/rsteube/carapace"
 	"github.com/rsteube/carapace/pkg/style"
 	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 )
 
+func TestPreInvoke(t *testing.T) {
+	Command(t, func() *cobra.Command {
+		rootCmd := &cobra.Command{}
+		rootCmd.CompletionOptions.DisableDefaultCmd = true
+		rootCmd.SetHelpCommand(nil)
+		carapace.Gen(rootCmd).PreInvoke(func(cmd *cobra.Command, flag *pflag.Flag, action carapace.Action) carapace.Action {
+			return action.Chdir("subdir1")
+		})
+		carapace.Gen(rootCmd).PositionalCompletion(
+			carapace.ActionFiles(),
+		)
+
+		subCmd := &cobra.Command{
+			Use: "sub",
+		}
+		carapace.Gen(subCmd).PreInvoke(func(cmd *cobra.Command, flag *pflag.Flag, action carapace.Action) carapace.Action {
+			return action.Chdir("subdir2")
+		})
+		carapace.Gen(subCmd).PositionalCompletion(
+			carapace.ActionFiles(),
+		)
+		rootCmd.AddCommand(subCmd)
+
+		return rootCmd
+	})(func(s *Sandbox) {
+		s.Files(
+			"subdir1/file1.txt", "",
+			"subdir1/subdir2/file2.txt", "",
+		)
+
+		s.Run("").
+			Expect(carapace.ActionValues(
+				"file1.txt",
+				"subdir2/").
+				StyleF(style.ForPath).
+				NoSpace('/').
+				Tag("files").
+				Chdir("subdir1"))
+
+		s.Run("sub", "").
+			Expect(carapace.ActionValues("file2.txt").
+				StyleF(style.ForPath).
+				NoSpace('/').
+				Tag("files").
+				Chdir("subdir1/subdir2/"))
+	})
+}
+
 func TestPreRun(t *testing.T) {
 	Command(t, func() *cobra.Command {
 		rootCmd := &cobra.Command{}
@@ -50,3 +100,20 @@ func TestPreRun(t *testing.T) {
 				Usage("sub flag"))
 	})
 }
+
+func TestEnv(t *testing.T) {
+	Command(t, func() *cobra.Command {
+		rootCmd := &cobra.Command{}
+		rootCmd.CompletionOptions.DisableDefaultCmd = true
+		rootCmd.SetHelpCommand(nil)
+		carapace.Gen(rootCmd).PositionalCompletion(
+			carapace.ActionCallback(func(c carapace.Context) carapace.Action {
+				return carapace.ActionValues(c.Getenv("LS_COLORS"))
+			}),
+		)
+		return rootCmd
+	})(func(s *Sandbox) {
+		s.Run("").
+			Expect(carapace.ActionValues(os.Getenv("LS_COLORS")))
+	})
+}