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 }