github.com/gechr/complete@v0.0.0-20191016221035-401475e3ce1e/complete_test.go (about)

     1  package complete
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  func TestCompleter_Complete(t *testing.T) {
    14  	initTests()
    15  
    16  	c := Command{
    17  		Sub: Commands{
    18  			"sub1": {
    19  				Flags: Flags{
    20  					"-flag1": PredictAnything,
    21  					"-flag2": PredictNothing,
    22  				},
    23  			},
    24  			"sub2": {
    25  				Flags: Flags{
    26  					"-flag2": PredictNothing,
    27  					"-flag3": PredictSet("opt1", "opt2", "opt12"),
    28  				},
    29  				Args: PredictFiles("*.md"),
    30  			},
    31  		},
    32  		Flags: Flags{
    33  			"-o": PredictFiles("*.txt"),
    34  		},
    35  		GlobalFlags: Flags{
    36  			"-h":       PredictNothing,
    37  			"-global1": PredictAnything,
    38  		},
    39  	}
    40  	cmp := New("cmd", c)
    41  
    42  	tests := []struct {
    43  		line  string
    44  		point int // -1 indicates len(line)
    45  		want  []string
    46  	}{
    47  		{
    48  			line:  "cmd ",
    49  			point: -1,
    50  			want:  []string{"sub1", "sub2"},
    51  		},
    52  		{
    53  			line:  "cmd -",
    54  			point: -1,
    55  			want:  []string{"-h", "-global1", "-o"},
    56  		},
    57  		{
    58  			line:  "cmd -h ",
    59  			point: -1,
    60  			want:  []string{"sub1", "sub2"},
    61  		},
    62  		{
    63  			line:  "cmd -global1 ", // global1 is known follow flag
    64  			point: -1,
    65  			want:  []string{},
    66  		},
    67  		{
    68  			line:  "cmd sub",
    69  			point: -1,
    70  			want:  []string{"sub1", "sub2"},
    71  		},
    72  		{
    73  			line:  "cmd sub1",
    74  			point: -1,
    75  			want:  []string{"sub1"},
    76  		},
    77  		{
    78  			line:  "cmd sub2",
    79  			point: -1,
    80  			want:  []string{"sub2"},
    81  		},
    82  		{
    83  			line:  "cmd sub1 ",
    84  			point: -1,
    85  			want:  []string{},
    86  		},
    87  		{
    88  			line:  "cmd sub1 -",
    89  			point: -1,
    90  			want:  []string{"-flag1", "-flag2", "-h", "-global1"},
    91  		},
    92  		{
    93  			line:  "cmd sub2 ",
    94  			point: -1,
    95  			want:  []string{"./", "dir/", "outer/", "readme.md"},
    96  		},
    97  		{
    98  			line:  "cmd sub2 ./",
    99  			point: -1,
   100  			want:  []string{"./", "./readme.md", "./dir/", "./outer/"},
   101  		},
   102  		{
   103  			line:  "cmd sub2 re",
   104  			point: -1,
   105  			want:  []string{"readme.md"},
   106  		},
   107  		{
   108  			line:  "cmd sub2 ./re",
   109  			point: -1,
   110  			want:  []string{"./readme.md"},
   111  		},
   112  		{
   113  			line:  "cmd sub2 -flag2 ",
   114  			point: -1,
   115  			want:  []string{"./", "dir/", "outer/", "readme.md"},
   116  		},
   117  		{
   118  			line:  "cmd sub1 -fl",
   119  			point: -1,
   120  			want:  []string{"-flag1", "-flag2"},
   121  		},
   122  		{
   123  			line:  "cmd sub1 -flag1",
   124  			point: -1,
   125  			want:  []string{"-flag1"},
   126  		},
   127  		{
   128  			line:  "cmd sub1 -flag1 ",
   129  			point: -1,
   130  			want:  []string{}, // flag1 is unknown follow flag
   131  		},
   132  		{
   133  			line:  "cmd sub1 -flag2 -",
   134  			point: -1,
   135  			want:  []string{"-flag1", "-flag2", "-h", "-global1"},
   136  		},
   137  		{
   138  			line:  "cmd -no-such-flag",
   139  			point: -1,
   140  			want:  []string{},
   141  		},
   142  		{
   143  			line:  "cmd -no-such-flag ",
   144  			point: -1,
   145  			want:  []string{"sub1", "sub2"},
   146  		},
   147  		{
   148  			line:  "cmd -no-such-flag -",
   149  			point: -1,
   150  			want:  []string{"-h", "-global1", "-o"},
   151  		},
   152  		{
   153  			line:  "cmd no-such-command",
   154  			point: -1,
   155  			want:  []string{},
   156  		},
   157  		{
   158  			line:  "cmd no-such-command ",
   159  			point: -1,
   160  			want:  []string{"sub1", "sub2"},
   161  		},
   162  		{
   163  			line:  "cmd -o ",
   164  			point: -1,
   165  			want:  []string{"a.txt", "b.txt", "c.txt", ".dot.txt", "./", "dir/", "outer/"},
   166  		},
   167  		{
   168  			line:  "cmd -o ./no-su",
   169  			point: -1,
   170  			want:  []string{},
   171  		},
   172  		{
   173  			line:  "cmd -o ./",
   174  			point: -1,
   175  			want:  []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
   176  		},
   177  		{
   178  			line:  "cmd -o=./",
   179  			point: -1,
   180  			want:  []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
   181  		},
   182  		{
   183  			line:  "cmd -o .",
   184  			point: -1,
   185  			want:  []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
   186  		},
   187  		{
   188  			line:  "cmd -o ./b",
   189  			point: -1,
   190  			want:  []string{"./b.txt"},
   191  		},
   192  		{
   193  			line:  "cmd -o=./b",
   194  			point: -1,
   195  			want:  []string{"./b.txt"},
   196  		},
   197  		{
   198  			line:  "cmd -o ./read",
   199  			point: -1,
   200  			want:  []string{},
   201  		},
   202  		{
   203  			line:  "cmd -o=./read",
   204  			point: -1,
   205  			want:  []string{},
   206  		},
   207  		{
   208  			line:  "cmd -o ./readme.md",
   209  			point: -1,
   210  			want:  []string{},
   211  		},
   212  		{
   213  			line:  "cmd -o ./readme.md ",
   214  			point: -1,
   215  			want:  []string{"sub1", "sub2"},
   216  		},
   217  		{
   218  			line:  "cmd -o=./readme.md ",
   219  			point: -1,
   220  			want:  []string{"sub1", "sub2"},
   221  		},
   222  		{
   223  			line:  "cmd -o sub2 -flag3 ",
   224  			point: -1,
   225  			want:  []string{"opt1", "opt2", "opt12"},
   226  		},
   227  		{
   228  			line:  "cmd -o sub2 -flag3 opt1",
   229  			point: -1,
   230  			want:  []string{"opt1", "opt12"},
   231  		},
   232  		{
   233  			line:  "cmd -o sub2 -flag3 opt",
   234  			point: -1,
   235  			want:  []string{"opt1", "opt2", "opt12"},
   236  		},
   237  		{
   238  			line: "cmd -o ./b foo",
   239  			//               ^
   240  			point: 10,
   241  			want:  []string{"./b.txt"},
   242  		},
   243  		{
   244  			line: "cmd -o=./b foo",
   245  			//               ^
   246  			point: 10,
   247  			want:  []string{"./b.txt"},
   248  		},
   249  		{
   250  			line: "cmd -o sub2 -flag3 optfoo",
   251  			//                           ^
   252  			point: 22,
   253  			want:  []string{"opt1", "opt2", "opt12"},
   254  		},
   255  		{
   256  			line: "cmd -o ",
   257  			//         ^
   258  			point: 4,
   259  			want:  []string{"sub1", "sub2"},
   260  		},
   261  	}
   262  
   263  	for _, tt := range tests {
   264  		t.Run(fmt.Sprintf("%s@%d", tt.line, tt.point), func(t *testing.T) {
   265  			got := runComplete(cmp, tt.line, tt.point)
   266  
   267  			sort.Strings(tt.want)
   268  			sort.Strings(got)
   269  
   270  			if !equalSlices(got, tt.want) {
   271  				t.Errorf("failed '%s'\ngot: %s\nwant: %s", t.Name(), got, tt.want)
   272  			}
   273  		})
   274  	}
   275  }
   276  
   277  func TestCompleter_Complete_SharedPrefix(t *testing.T) {
   278  	initTests()
   279  
   280  	c := Command{
   281  		Sub: Commands{
   282  			"status": {
   283  				Flags: Flags{
   284  					"-f3": PredictNothing,
   285  				},
   286  			},
   287  			"job": {
   288  				Sub: Commands{
   289  					"status": {
   290  						Flags: Flags{
   291  							"-f4": PredictNothing,
   292  						},
   293  					},
   294  				},
   295  			},
   296  		},
   297  		Flags: Flags{
   298  			"-o": PredictFiles("*.txt"),
   299  		},
   300  		GlobalFlags: Flags{
   301  			"-h":       PredictNothing,
   302  			"-global1": PredictAnything,
   303  		},
   304  	}
   305  
   306  	cmp := New("cmd", c)
   307  
   308  	tests := []struct {
   309  		line  string
   310  		point int // -1 indicates len(line)
   311  		want  []string
   312  	}{
   313  		{
   314  			line:  "cmd ",
   315  			point: -1,
   316  			want:  []string{"status", "job"},
   317  		},
   318  		{
   319  			line:  "cmd -",
   320  			point: -1,
   321  			want:  []string{"-h", "-global1", "-o"},
   322  		},
   323  		{
   324  			line:  "cmd j",
   325  			point: -1,
   326  			want:  []string{"job"},
   327  		},
   328  		{
   329  			line:  "cmd job ",
   330  			point: -1,
   331  			want:  []string{"status"},
   332  		},
   333  		{
   334  			line:  "cmd job -",
   335  			point: -1,
   336  			want:  []string{"-h", "-global1"},
   337  		},
   338  		{
   339  			line:  "cmd job status ",
   340  			point: -1,
   341  			want:  []string{},
   342  		},
   343  		{
   344  			line:  "cmd job status -",
   345  			point: -1,
   346  			want:  []string{"-f4", "-h", "-global1"},
   347  		},
   348  	}
   349  
   350  	for _, tt := range tests {
   351  		t.Run(tt.line, func(t *testing.T) {
   352  			got := runComplete(cmp, tt.line, tt.point)
   353  
   354  			sort.Strings(tt.want)
   355  			sort.Strings(got)
   356  
   357  			if !equalSlices(got, tt.want) {
   358  				t.Errorf("failed '%s'\ngot = %s\nwant: %s", t.Name(), got, tt.want)
   359  			}
   360  		})
   361  	}
   362  }
   363  
   364  // runComplete runs the complete login for test purposes
   365  // it gets the complete struct and command line arguments and returns
   366  // the complete options
   367  func runComplete(c *Complete, line string, point int) (completions []string) {
   368  	if point == -1 {
   369  		point = len(line)
   370  	}
   371  	os.Setenv(envLine, line)
   372  	os.Setenv(envPoint, strconv.Itoa(point))
   373  	b := bytes.NewBuffer(nil)
   374  	c.Out = b
   375  	c.Complete()
   376  	completions = parseOutput(b.String())
   377  	return
   378  }
   379  
   380  func parseOutput(output string) []string {
   381  	lines := strings.Split(output, "\n")
   382  	options := []string{}
   383  	for _, l := range lines {
   384  		if l != "" {
   385  			options = append(options, l)
   386  		}
   387  	}
   388  	return options
   389  }
   390  
   391  func equalSlices(a, b []string) bool {
   392  	if len(a) != len(b) {
   393  		return false
   394  	}
   395  	for i := range a {
   396  		if a[i] != b[i] {
   397  			return false
   398  		}
   399  	}
   400  	return true
   401  }