github.com/ChicK00o/awgo@v0.29.4/feedback_test.go (about)

     1  // Copyright (c) 2018 Dean Jackson <deanishe@deanishe.net>
     2  // MIT Licence - http://opensource.org/licenses/MIT
     3  
     4  package aw
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestItem_Icon(t *testing.T) {
    16  	t.Parallel()
    17  
    18  	it := Item{}
    19  	it.Icon(&Icon{"first", "fileicon"})
    20  	assert.Equal(t, "first", it.icon.Value, "unexpected icon Value")
    21  	assert.Equal(t, IconType("fileicon"), it.icon.Type, "unexpected icon Type")
    22  }
    23  
    24  func p(s string) *string { return &s }
    25  
    26  // TestFeedback_IsEmpty verifies empty feedback.
    27  func TestFeedback_IsEmpty(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	fb := NewFeedback()
    31  	assert.True(t, fb.IsEmpty(), "feedback not empty")
    32  
    33  	fb.NewItem("test")
    34  	assert.False(t, fb.IsEmpty(), "feedback empty")
    35  }
    36  
    37  func TestItem_MarshalJSON(t *testing.T) {
    38  	t.Parallel()
    39  
    40  	tests := []struct {
    41  		in *Item
    42  		x  string
    43  	}{
    44  		// Minimal item
    45  		{in: &Item{title: "title"},
    46  			x: `{"title":"title","valid":false}`},
    47  		// With UID
    48  		{in: &Item{title: "title", uid: p("xxx-yyy")},
    49  			x: `{"title":"title","uid":"xxx-yyy","valid":false}`},
    50  		// With autocomplete
    51  		{in: &Item{title: "title", autocomplete: p("xxx-yyy")},
    52  			x: `{"title":"title","autocomplete":"xxx-yyy","valid":false}`},
    53  		// With empty autocomplete
    54  		{in: &Item{title: "title", autocomplete: p("")},
    55  			x: `{"title":"title","autocomplete":"","valid":false}`},
    56  		// With subtitle
    57  		{in: &Item{title: "title", subtitle: p("subtitle")},
    58  			x: `{"title":"title","subtitle":"subtitle","valid":false}`},
    59  		// Alternate subtitle
    60  		{in: &Item{title: "title", subtitle: p("subtitle"),
    61  			mods: map[string]*Modifier{
    62  				"cmd": {
    63  					Key:      "cmd",
    64  					subtitle: p("command sub")}}},
    65  			x: `{"title":"title","subtitle":"subtitle",` +
    66  				`"valid":false,"mods":{"cmd":{"subtitle":"command sub"}}}`},
    67  		// Valid item
    68  		{in: &Item{title: "title", valid: true},
    69  			x: `{"title":"title","valid":true}`},
    70  		// With arg
    71  		{in: &Item{title: "title", arg: []string{"arg1"}},
    72  			x: `{"title":"title","arg":"arg1","valid":false}`},
    73  		// Empty arg
    74  		{in: &Item{title: "title", arg: []string{""}},
    75  			x: `{"title":"title","arg":"","valid":false}`},
    76  		// Multiple args
    77  		{in: &Item{title: "title", arg: []string{"one", "two"}},
    78  			x: `{"title":"title","arg":["one","two"],"valid":false}`},
    79  		// Arg contains escapes
    80  		{in: &Item{title: "title", arg: []string{"\x00arg\x00"}},
    81  			x: `{"title":"title","arg":"\u0000arg\u0000","valid":false}`},
    82  		// Valid with arg
    83  		{in: &Item{title: "title", arg: []string{"arg1"}, valid: true},
    84  			x: `{"title":"title","arg":"arg1","valid":true}`},
    85  		// With icon
    86  		{in: &Item{title: "title",
    87  			icon: &Icon{Value: "icon.png", Type: ""}},
    88  			x: `{"title":"title","valid":false,"icon":{"path":"icon.png"}}`},
    89  		// With file icon
    90  		{in: &Item{title: "title",
    91  			icon: &Icon{Value: "icon.png", Type: "fileicon"}},
    92  			x: `{"title":"title","valid":false,"icon":{"path":"icon.png","type":"fileicon"}}`},
    93  		// With filetype icon
    94  		{in: &Item{title: "title",
    95  			icon: &Icon{Value: "public.folder", Type: "filetype"}},
    96  			x: `{"title":"title","valid":false,"icon":{"path":"public.folder","type":"filetype"}}`},
    97  		// With type = file
    98  		{in: &Item{title: "title", file: true},
    99  			x: `{"title":"title","valid":false,"type":"file"}`},
   100  		// With copy text
   101  		{in: &Item{title: "title", copytext: p("copy")},
   102  			x: `{"title":"title","valid":false,"text":{"copy":"copy"}}`},
   103  		// With large text
   104  		{in: &Item{title: "title", largetype: p("large")},
   105  			x: `{"title":"title","valid":false,"text":{"largetype":"large"}}`},
   106  		// With copy and large text
   107  		{in: &Item{title: "title", copytext: p("copy"), largetype: p("large")},
   108  			x: `{"title":"title","valid":false,"text":{"copy":"copy","largetype":"large"}}`},
   109  		// With arg and variable
   110  		{in: &Item{title: "title", arg: []string{"value"}, vars: map[string]string{"foo": "bar"}},
   111  			x: `{"title":"title","arg":"value","valid":false,"variables":{"foo":"bar"}}`},
   112  		// With match
   113  		{in: &Item{title: "title", match: p("one two three")},
   114  			x: `{"title":"title","match":"one two three","valid":false}`},
   115  		// With quicklook
   116  		{in: &Item{title: "title", ql: p("http://www.example.com")},
   117  			x: `{"title":"title","valid":false,"quicklookurl":"http://www.example.com"}`},
   118  		// With action
   119  		{in: &Item{title: "title", actions: map[string][]string{"auto": {"one", "two"}}},
   120  			x: `{"title":"title","valid":false,"action":{"auto":["one","two"]}}`},
   121  	}
   122  
   123  	for i, td := range tests {
   124  		td := td // capture variable
   125  		t.Run(fmt.Sprintf("MarshalItem(%d)", i), func(t *testing.T) {
   126  			t.Parallel()
   127  			data, err := json.Marshal(td.in)
   128  			assert.Nil(t, err, "marshal Item failed")
   129  			assert.Equal(t, td.x, string(data), "unexpected JSON")
   130  		})
   131  	}
   132  }
   133  
   134  func TestModifier_MarshalJSON(t *testing.T) {
   135  	t.Parallel()
   136  
   137  	tests := []struct {
   138  		in *Modifier
   139  		x  string
   140  	}{
   141  		// Empty item
   142  		{in: &Modifier{}, x: `{}`},
   143  		// With arg
   144  		{in: &Modifier{arg: []string{"title"}}, x: `{"arg":"title"}`},
   145  		// Empty arg
   146  		{in: &Modifier{arg: []string{""}}, x: `{"arg":""}`},
   147  		// Multiple args
   148  		{in: &Modifier{arg: []string{"one", "two"}}, x: `{"arg":["one","two"]}`},
   149  		// With subtitle
   150  		{in: &Modifier{subtitle: p("sub here")}, x: `{"subtitle":"sub here"}`},
   151  		// valid
   152  		{in: &Modifier{valid: true}, x: `{"valid":true}`},
   153  		// icon
   154  		{in: &Modifier{icon: &Icon{"icon.png", ""}}, x: `{"icon":{"path":"icon.png"}}`},
   155  		// With all
   156  		{in: &Modifier{
   157  			arg:      []string{"title"},
   158  			subtitle: p("sub here"),
   159  			valid:    true,
   160  		},
   161  			x: `{"arg":"title","subtitle":"sub here","valid":true}`},
   162  		// With variable
   163  		{in: &Modifier{
   164  			arg:      []string{"title"},
   165  			subtitle: p("sub here"),
   166  			valid:    true,
   167  			vars:     map[string]string{"foo": "bar"},
   168  		},
   169  			x: `{"arg":"title","subtitle":"sub here","valid":true,"variables":{"foo":"bar"}}`},
   170  	}
   171  
   172  	for i, td := range tests {
   173  		td := td // capture variable
   174  		t.Run(fmt.Sprintf("MarshalModifier(%d)", i), func(t *testing.T) {
   175  			t.Parallel()
   176  			data, err := json.Marshal(td.in)
   177  			assert.Nil(t, err, "marshal Modifier failed")
   178  			assert.Equal(t, td.x, string(data), "unexpected JSON")
   179  		})
   180  	}
   181  }
   182  
   183  func TestArgVars_MarshalJSON(t *testing.T) {
   184  	t.Parallel()
   185  
   186  	var tests = []struct {
   187  		in *ArgVars
   188  		x  string
   189  	}{
   190  		// Empty
   191  		{in: &ArgVars{}, x: `""`},
   192  		// With arg
   193  		{in: &ArgVars{arg: []string{"title"}}, x: `"title"`},
   194  		// With multiple args
   195  		{in: &ArgVars{arg: []string{"one", "two"}},
   196  			x: `{"alfredworkflow":{"arg":["one","two"]}}`},
   197  		// With non-ASCII arg
   198  		{in: &ArgVars{arg: []string{"fübär"}}, x: `"fübär"`},
   199  		// With escapes
   200  		{in: &ArgVars{arg: []string{"\x00"}}, x: `"\u0000"`},
   201  		// With variable
   202  		{in: &ArgVars{vars: map[string]string{"foo": "bar"}},
   203  			x: `{"alfredworkflow":{"variables":{"foo":"bar"}}}`},
   204  		// Multiple variables
   205  		{in: &ArgVars{vars: map[string]string{"foo": "bar", "ducky": "fuzz"}},
   206  			x: `{"alfredworkflow":{"variables":{"ducky":"fuzz","foo":"bar"}}}`},
   207  		// Multiple variables and arg
   208  		{in: &ArgVars{arg: []string{"title"}, vars: map[string]string{"foo": "bar", "ducky": "fuzz"}},
   209  			x: `{"alfredworkflow":{"arg":"title","variables":{"ducky":"fuzz","foo":"bar"}}}`},
   210  	}
   211  
   212  	for i, td := range tests {
   213  		td := td // capture variable
   214  		t.Run(fmt.Sprintf("MarshalArgVar(%d)", i), func(t *testing.T) {
   215  			t.Parallel()
   216  			data, err := json.Marshal(td.in)
   217  			assert.Nil(t, err, "marshal ArgVars failed")
   218  			assert.Equal(t, td.x, string(data), "unexpected JSON")
   219  		})
   220  	}
   221  }
   222  
   223  // Simple arg marshalled to single string
   224  func TestArgVars_String(t *testing.T) {
   225  	t.Parallel()
   226  
   227  	tests := []struct {
   228  		in *ArgVars
   229  		x  string
   230  	}{
   231  		// Empty
   232  		{in: &ArgVars{}, x: ""},
   233  		// With arg
   234  		{in: &ArgVars{arg: []string{"title"}}, x: "title"},
   235  		// With multiple args
   236  		{in: &ArgVars{arg: []string{"one", "two"}},
   237  			x: `{"alfredworkflow":{"arg":["one","two"]}}`},
   238  		// With non-ASCII
   239  		{in: &ArgVars{arg: []string{"fübär"}},
   240  			x: "fübär"},
   241  		// With escapes
   242  		{in: &ArgVars{arg: []string{"\x00"}},
   243  			x: "\x00"},
   244  	}
   245  
   246  	for i, td := range tests {
   247  		td := td // capture variable
   248  		t.Run(fmt.Sprintf("StringifyArg(%d)", i), func(t *testing.T) {
   249  			t.Parallel()
   250  			v, err := td.in.String()
   251  			assert.Nil(t, err, "stringify ArgVars failed")
   252  			assert.Equal(t, td.x, v, "unexpected value")
   253  		})
   254  	}
   255  }
   256  
   257  // Vars set correctly
   258  func TestArgVars_Vars(t *testing.T) {
   259  	t.Parallel()
   260  
   261  	vars := map[string]string{
   262  		"key1": "val1",
   263  		"key2": "val2",
   264  		"key3": "val3",
   265  		"key4": "val4",
   266  		"key5": "val5",
   267  	}
   268  
   269  	av := NewArgVars()
   270  	for k, v := range vars {
   271  		av.Var(k, v)
   272  	}
   273  
   274  	assert.Equal(t, vars, av.Vars(), "Unexpected Vars")
   275  }
   276  
   277  // Marshal Feedback to JSON
   278  func TestFeedback_MarshalJSON(t *testing.T) {
   279  	t.Parallel()
   280  
   281  	// Empty feedback
   282  	fb := NewFeedback()
   283  	want := `{"items":[]}`
   284  	got, err := json.Marshal(fb)
   285  	assert.Nil(t, err, "marshal Feedback failed")
   286  	assert.Equal(t, string(got), want, "unexpected value")
   287  
   288  	// Feedback with item
   289  	want = `{"items":[{"title":"item 1","valid":false}]}`
   290  	fb.NewItem("item 1")
   291  
   292  	got, err = json.Marshal(fb)
   293  	assert.Nil(t, err, "marshal Feedback failed")
   294  	assert.Equal(t, string(got), want, "unexpected value")
   295  }
   296  
   297  // Modifier inherits variables from parent Item
   298  func TestModifierInheritVars(t *testing.T) {
   299  	t.Parallel()
   300  
   301  	fb := NewFeedback()
   302  	it := fb.NewItem("title")
   303  	it.Var("foo", "bar")
   304  	m := it.NewModifier("cmd")
   305  	assert.Equal(t, "bar", m.Vars()["foo"], "unexpected var value")
   306  }
   307  
   308  // Empty/invalid modifiers
   309  func TestEmptyModifiersIgnored(t *testing.T) {
   310  	t.Parallel()
   311  
   312  	fb := NewFeedback()
   313  
   314  	tests := []struct {
   315  		keys []string
   316  		ok   bool
   317  	}{
   318  		{[]string{}, false},
   319  		{[]string{""}, false},
   320  		{[]string{"", ""}, false},
   321  		{[]string{"rick flair"}, false},
   322  		{[]string{"andre the giant", ""}, false},
   323  		{[]string{"ultimate warrior", "cmd"}, true},
   324  		{[]string{"ctrl", "", "giant haystacks"}, true},
   325  	}
   326  
   327  	for _, td := range tests {
   328  		td := td
   329  		t.Run(fmt.Sprintf("%v", td.keys), func(t *testing.T) {
   330  			it := fb.NewItem("title")
   331  			require.Equal(t, 0, len(it.mods), "unexpected modifier count")
   332  
   333  			_ = it.NewModifier(td.keys...)
   334  			if td.ok {
   335  				assert.Equal(t, 1, len(it.mods), "unexpected modifier count")
   336  			} else {
   337  				assert.Equal(t, 0, len(it.mods), "unexpected modifier count")
   338  			}
   339  		})
   340  	}
   341  }
   342  
   343  // Combined modifiers
   344  func TestMultipleModifiers(t *testing.T) {
   345  	t.Parallel()
   346  
   347  	fb := NewFeedback()
   348  	it := fb.NewItem("title")
   349  
   350  	tests := []struct {
   351  		keys []string
   352  		x    string
   353  	}{
   354  		{[]string{"cmd"}, "cmd"},
   355  		{[]string{"alt"}, "alt"},
   356  		{[]string{"opt"}, "alt"},
   357  		{[]string{"fn"}, "fn"},
   358  		{[]string{"shift"}, "shift"},
   359  		{[]string{"alt", "cmd"}, "alt+cmd"},
   360  		{[]string{"cmd", "alt"}, "alt+cmd"},
   361  		{[]string{"cmd", "opt"}, "alt+cmd"},
   362  		{[]string{"cmd", "opt", "ctrl"}, "alt+cmd+ctrl"},
   363  		{[]string{"cmd", "opt", "shift"}, "alt+cmd+shift"},
   364  		// invalid keys ignored
   365  		{[]string{}, ""},
   366  		{[]string{""}, ""},
   367  		{[]string{"shift", "cmd", ""}, "cmd+shift"},
   368  		{[]string{"shift", "ctrl", "hulk hogan"}, "ctrl+shift"},
   369  		{[]string{"shift", "undertaker", "cmd", ""}, "cmd+shift"},
   370  	}
   371  
   372  	for _, td := range tests {
   373  		td := td
   374  		t.Run(fmt.Sprintf("%v", td.keys), func(t *testing.T) {
   375  			m := it.NewModifier(td.keys...)
   376  			assert.Equal(t, td.x, m.Key, "unexpected modifier")
   377  		})
   378  	}
   379  }
   380  
   381  // TestModifierShortcuts verifies creation shortcut methods.
   382  func TestModifierShortcuts(t *testing.T) {
   383  	t.Parallel()
   384  
   385  	it := &Item{}
   386  	tests := []struct {
   387  		m *Modifier
   388  		k string
   389  	}{
   390  		{it.Cmd(), ModCmd},
   391  		{it.Opt(), ModOpt},
   392  		{it.Shift(), ModShift},
   393  		{it.Ctrl(), ModCtrl},
   394  		{it.Fn(), ModFn},
   395  	}
   396  
   397  	for _, td := range tests {
   398  		assert.Equal(t, td.k, td.m.Key, "Bad modkey for %q", td.k)
   399  	}
   400  }
   401  
   402  // TestFeedback_Rerun verifies that rerun is properly set.
   403  func TestFeedback_Rerun(t *testing.T) {
   404  	t.Parallel()
   405  
   406  	fb := NewFeedback()
   407  
   408  	fb.Rerun(1.5)
   409  
   410  	want := `{"rerun":1.5,"items":[]}`
   411  	got, err := json.Marshal(fb)
   412  	assert.Nil(t, err, "marshal Feedback failed")
   413  	assert.Equal(t, string(got), want, "unexpected value")
   414  }
   415  
   416  // Vars are properly inherited by Items and Modifiers
   417  func TestFeedback_Vars(t *testing.T) {
   418  	t.Parallel()
   419  
   420  	fb := NewFeedback()
   421  
   422  	fb.Var("foo", "bar")
   423  	if fb.Vars()["foo"] != "bar" {
   424  		t.Fatalf("Feedback var has wrong value. Expected=bar, Received=%v", fb.Vars()["foo"])
   425  	}
   426  
   427  	want := `{"variables":{"foo":"bar"},"items":[]}`
   428  	got, err := json.Marshal(fb)
   429  	assert.Nil(t, err, "marshal Feedback failed")
   430  	assert.Equal(t, string(got), want, "unexpected value")
   431  
   432  	// Top-level vars are inherited
   433  	it := fb.NewItem("title")
   434  	assert.Equal(t, "bar", it.Vars()["foo"], "unexpected var")
   435  
   436  	// Modifier inherits Item and top-level vars
   437  	it.Var("baz", "qux")
   438  	m := it.NewModifier("cmd")
   439  	assert.Equal(t, "qux", m.Vars()["baz"], "unexpected var")
   440  	assert.Equal(t, "bar", m.Vars()["foo"], "unexpected var")
   441  }
   442  
   443  // Item methods set fields correctly
   444  func TestItem_methods(t *testing.T) {
   445  	t.Parallel()
   446  
   447  	var (
   448  		title        = "title"
   449  		subtitle     = "subtitle"
   450  		match        = "match"
   451  		uid          = "uid"
   452  		autocomplete = "autocomplete"
   453  		arg          = []string{"arg"}
   454  		valid        = true
   455  		copytext     = "copytext"
   456  		largetype    = "largetype"
   457  		qlURL        = "http://www.example.com"
   458  	)
   459  
   460  	it := &Item{}
   461  
   462  	assert.Equal(t, "", it.title, "Non-empty title")
   463  	assert.Nil(t, it.subtitle, "Non-nil subtitle")
   464  	assert.Nil(t, it.match, "Non-nil match")
   465  	assert.Nil(t, it.uid, "Non-nil UID")
   466  	assert.Nil(t, it.autocomplete, "Non-nil autocomplete")
   467  	assert.Nil(t, it.arg, "Non-nil arg")
   468  	assert.Nil(t, it.copytext, "Non-nil copytext")
   469  	assert.Nil(t, it.largetype, "Non-nil largetype")
   470  	assert.Nil(t, it.ql, "Non-nil quicklook")
   471  
   472  	it.Title(title).
   473  		Subtitle(subtitle).
   474  		Match(match).
   475  		UID(uid).
   476  		Autocomplete(autocomplete).
   477  		Arg(arg...).
   478  		Valid(valid).
   479  		Copytext(copytext).
   480  		Largetype(largetype).
   481  		Quicklook(qlURL)
   482  
   483  	assert.Equal(t, title, it.title, "Bad title")
   484  	assert.Equal(t, subtitle, *it.subtitle, "Bad subtitle")
   485  	assert.Equal(t, match, *it.match, "Bad match")
   486  	assert.Equal(t, uid, *it.uid, "Bad UID")
   487  	assert.Equal(t, autocomplete, *it.autocomplete, "Bad autocomplete")
   488  	assert.Equal(t, arg, it.arg, "Bad arg")
   489  	assert.Equal(t, valid, valid, "Bad valid")
   490  	assert.Equal(t, copytext, *it.copytext, "Bad copytext")
   491  	assert.Equal(t, largetype, *it.largetype, "Bad largetext")
   492  	assert.Equal(t, qlURL, *it.ql, "Bad quicklook URL")
   493  }
   494  
   495  // TestModifier_methods verifies Modifier methods.
   496  func TestModifier_methods(t *testing.T) {
   497  	var (
   498  		key      = ModCmd
   499  		arg      = []string{"arg"}
   500  		subtitle = "subtitle"
   501  		valid    = true
   502  		icon     = IconAccount
   503  	)
   504  
   505  	m := &Modifier{}
   506  	assert.Equal(t, "", m.Key, "Non-empty key")
   507  	assert.Nil(t, m.arg, "Non-nil arg")
   508  	assert.Nil(t, m.subtitle, "Non-nil subtitle")
   509  	assert.False(t, m.valid, "Bad valid")
   510  	assert.Nil(t, m.icon, "Bad icon")
   511  
   512  	m.Key = key
   513  	m.Subtitle(subtitle).
   514  		Arg(arg...).
   515  		Valid(valid).
   516  		Icon(icon)
   517  
   518  	assert.Equal(t, key, m.Key, "Bad key")
   519  	assert.Equal(t, arg, m.arg, "Bad arg")
   520  	assert.Equal(t, subtitle, *m.subtitle, "Bad subtitle")
   521  	assert.Equal(t, valid, m.valid, "Bad valid")
   522  	assert.Equal(t, icon.Type, m.icon.Type, "Bad icon type")
   523  	assert.Equal(t, icon.Value, m.icon.Value, "Bad icon value")
   524  }
   525  
   526  // Sorts Feedback.Items
   527  func TestFeedback_Sort(t *testing.T) {
   528  	for _, td := range feedbackTitles {
   529  		fb := NewFeedback()
   530  		for _, s := range td.in {
   531  			fb.NewItem(s)
   532  		}
   533  		r := fb.Sort(td.q)
   534  		for i, it := range fb.Items {
   535  			assert.Equal(t, td.out[i], it.title, "unexpected title")
   536  			assert.Equal(t, td.m[i], r[i].Match, "unexpected match")
   537  		}
   538  	}
   539  }
   540  
   541  var feedbackTitles = []struct {
   542  	q   string
   543  	in  []string
   544  	out []string
   545  	m   []bool
   546  }{
   547  	{
   548  		q:   "got",
   549  		in:  []string{"game of thrones", "no match", "got milk?", "got"},
   550  		out: []string{"got", "game of thrones", "got milk?", "no match"},
   551  		m:   []bool{true, true, true, false},
   552  	},
   553  	{
   554  		q:   "of",
   555  		in:  []string{"out of time", "spelunking", "OmniFocus", "game of thrones"},
   556  		out: []string{"OmniFocus", "out of time", "game of thrones", "spelunking"},
   557  		m:   []bool{true, true, true, false},
   558  	},
   559  	{
   560  		q:   "safa",
   561  		in:  []string{"see all fellows' armpits", "Safari", "french canada", "spanish harlem"},
   562  		out: []string{"Safari", "see all fellows' armpits", "spanish harlem", "french canada"},
   563  		m:   []bool{true, true, false, false},
   564  	},
   565  	// sorting is stable
   566  	{
   567  		q:   "test",
   568  		in:  []string{"test #2", "test #1", "test #10", "test #3"},
   569  		out: []string{"test #2", "test #1", "test #3", "test #10"},
   570  		m:   []bool{true, true, true, true},
   571  	},
   572  }
   573  
   574  var filterTitles = []struct {
   575  	q   string
   576  	in  []string
   577  	out []string
   578  }{
   579  	{
   580  		q:   "got",
   581  		in:  []string{"game of thrones", "no match", "got milk?", "got"},
   582  		out: []string{"got", "game of thrones", "got milk?"},
   583  	},
   584  	{
   585  		q:   "of",
   586  		in:  []string{"out of time", "spelunking", "OmniFocus", "game of thrones"},
   587  		out: []string{"OmniFocus", "out of time", "game of thrones"},
   588  	},
   589  	{
   590  		q:   "safa",
   591  		in:  []string{"see all fellows' armpits", "Safari", "french canada", "spanish harlem"},
   592  		out: []string{"Safari", "see all fellows' armpits"},
   593  	},
   594  }
   595  
   596  // Filter Feedback.Items
   597  func TestFeedback_Filter(t *testing.T) {
   598  	for _, td := range filterTitles {
   599  		fb := NewFeedback()
   600  		for _, s := range td.in {
   601  			fb.NewItem(s)
   602  		}
   603  		fb.Filter(td.q)
   604  		assert.Equal(t, len(td.out), len(fb.Items), "unexpected result count")
   605  		for i, it := range fb.Items {
   606  			assert.Equal(t, td.out[i], it.title, "unexpected title")
   607  		}
   608  	}
   609  }