github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+incompatible/util/ui/ui_test.go (about) 1 package ui_test 2 3 import ( 4 "errors" 5 "strings" 6 "time" 7 8 "code.cloudfoundry.org/cli/util/configv3" 9 . "code.cloudfoundry.org/cli/util/ui" 10 "code.cloudfoundry.org/cli/util/ui/uifakes" 11 . "github.com/onsi/ginkgo" 12 . "github.com/onsi/gomega" 13 . "github.com/onsi/gomega/gbytes" 14 ) 15 16 var _ = Describe("UI", func() { 17 var ( 18 ui *UI 19 fakeConfig *uifakes.FakeConfig 20 out *Buffer 21 ) 22 23 BeforeEach(func() { 24 fakeConfig = new(uifakes.FakeConfig) 25 fakeConfig.ColorEnabledReturns(configv3.ColorEnabled) 26 27 var err error 28 ui, err = NewUI(fakeConfig) 29 Expect(err).NotTo(HaveOccurred()) 30 31 out = NewBuffer() 32 ui.Out = out 33 ui.Err = NewBuffer() 34 }) 35 36 It("sets the TimezoneLocation to the local timezone", func() { 37 location := time.Now().Location() 38 Expect(ui.TimezoneLocation).To(Equal(location)) 39 }) 40 41 Describe("DisplayBoolPrompt", func() { 42 var inBuffer *Buffer 43 44 BeforeEach(func() { 45 inBuffer = NewBuffer() 46 ui.In = inBuffer 47 }) 48 49 It("displays the passed in string", func() { 50 _, _ = ui.DisplayBoolPrompt(false, "some-prompt", nil) 51 Expect(ui.Out).To(Say("some-prompt \\[yN\\]:")) 52 }) 53 54 Context("when the user chooses yes", func() { 55 BeforeEach(func() { 56 _, err := inBuffer.Write([]byte("y\n")) 57 Expect(err).ToNot(HaveOccurred()) 58 }) 59 60 It("returns true", func() { 61 response, err := ui.DisplayBoolPrompt(false, "some-prompt", nil) 62 Expect(err).ToNot(HaveOccurred()) 63 Expect(response).To(BeTrue()) 64 }) 65 }) 66 67 Context("when the user chooses no", func() { 68 BeforeEach(func() { 69 _, err := inBuffer.Write([]byte("n\n")) 70 Expect(err).ToNot(HaveOccurred()) 71 }) 72 73 It("returns false", func() { 74 response, err := ui.DisplayBoolPrompt(false, "some-prompt", nil) 75 Expect(err).ToNot(HaveOccurred()) 76 Expect(response).To(BeFalse()) 77 }) 78 }) 79 80 Context("when the user chooses the default", func() { 81 BeforeEach(func() { 82 _, err := inBuffer.Write([]byte("\n")) 83 Expect(err).ToNot(HaveOccurred()) 84 }) 85 86 Context("when the default is true", func() { 87 It("returns true", func() { 88 response, err := ui.DisplayBoolPrompt(true, "some-prompt", nil) 89 Expect(err).ToNot(HaveOccurred()) 90 Expect(response).To(BeTrue()) 91 }) 92 }) 93 94 Context("when the default is false", func() { 95 It("returns false", func() { 96 response, err := ui.DisplayBoolPrompt(false, "some-prompt", nil) 97 Expect(err).ToNot(HaveOccurred()) 98 Expect(response).To(BeFalse()) 99 }) 100 }) 101 }) 102 103 Context("when the interact library returns an error", func() { 104 It("returns the error", func() { 105 _, err := inBuffer.Write([]byte("invalid\n")) 106 Expect(err).ToNot(HaveOccurred()) 107 _, err = ui.DisplayBoolPrompt(false, "some-prompt", nil) 108 Expect(err).To(HaveOccurred()) 109 }) 110 }) 111 }) 112 113 Describe("DisplayError", func() { 114 Context("when passed a TranslatableError", func() { 115 var fakeTranslateErr *uifakes.FakeTranslatableError 116 117 BeforeEach(func() { 118 fakeTranslateErr = new(uifakes.FakeTranslatableError) 119 fakeTranslateErr.TranslateReturns("I am an error") 120 121 ui.DisplayError(fakeTranslateErr) 122 }) 123 124 It("displays the error to ui.Err and displays FAILED in bold red to ui.Out", func() { 125 Expect(ui.Err).To(Say("I am an error\n")) 126 Expect(ui.Out).To(Say("\x1b\\[31;1mFAILED\x1b\\[0m\n")) 127 }) 128 129 Context("when the locale is not set to english", func() { 130 It("translates the error text", func() { 131 Expect(fakeTranslateErr.TranslateCallCount()).To(Equal(1)) 132 Expect(fakeTranslateErr.TranslateArgsForCall(0)).NotTo(BeNil()) 133 }) 134 }) 135 }) 136 137 Context("when passed a generic error", func() { 138 It("displays the error text to ui.Err and displays FAILED in bold red to ui.Out", func() { 139 ui.DisplayError(errors.New("I am a BANANA!")) 140 Expect(ui.Err).To(Say("I am a BANANA!\n")) 141 Expect(ui.Out).To(Say("\x1b\\[31;1mFAILED\x1b\\[0m\n")) 142 }) 143 }) 144 }) 145 146 Describe("DisplayHeader", func() { 147 It("displays the header colorized and bolded to ui.Out", func() { 148 ui.DisplayHeader("some-header") 149 Expect(ui.Out).To(Say("\x1b\\[1msome-header\x1b\\[0m")) 150 }) 151 152 Context("when the locale is not set to english", func() { 153 BeforeEach(func() { 154 fakeConfig.LocaleReturns("fr-FR") 155 156 var err error 157 ui, err = NewUI(fakeConfig) 158 Expect(err).NotTo(HaveOccurred()) 159 160 ui.Out = NewBuffer() 161 }) 162 163 It("displays the translated header colorized and bolded to ui.Out", func() { 164 ui.DisplayHeader("FEATURE FLAGS") 165 Expect(ui.Out).To(Say("\x1b\\[1mINDICATEURS DE FONCTION\x1b\\[0m")) 166 }) 167 }) 168 }) 169 170 Describe("DisplayKeyValueTable", func() { 171 JustBeforeEach(func() { 172 ui.DisplayKeyValueTable(" ", 173 [][]string{ 174 {"wut0:", ""}, 175 {"wut1:", "hi hi"}, 176 {"wut2:", strings.Repeat("a", 9)}, 177 {"wut3:", "hi hi " + strings.Repeat("a", 9)}, 178 {"wut4:", strings.Repeat("a", 15) + " " + strings.Repeat("b", 15)}, 179 }, 180 2) 181 }) 182 183 Context("in a TTY", func() { 184 BeforeEach(func() { 185 ui.IsTTY = true 186 ui.TerminalWidth = 20 187 }) 188 189 It("displays a table with the last column wrapping according to width", func() { 190 Expect(ui.Out).To(Say(" wut0: " + "\n")) 191 Expect(ui.Out).To(Say(" wut1: " + "hi hi\n")) 192 Expect(ui.Out).To(Say(" wut2: " + strings.Repeat("a", 9) + "\n")) 193 Expect(ui.Out).To(Say(" wut3: hi hi\n")) 194 Expect(ui.Out).To(Say(" " + strings.Repeat("a", 9) + "\n")) 195 Expect(ui.Out).To(Say(" wut4: " + strings.Repeat("a", 15) + "\n")) 196 Expect(ui.Out).To(Say(" " + strings.Repeat("b", 15) + "\n")) 197 }) 198 }) 199 }) 200 201 Describe("DisplayLogMessage", func() { 202 var message *uifakes.FakeLogMessage 203 204 BeforeEach(func() { 205 var err error 206 ui.TimezoneLocation, err = time.LoadLocation("America/Los_Angeles") 207 Expect(err).NotTo(HaveOccurred()) 208 209 message = new(uifakes.FakeLogMessage) 210 message.MessageReturns("This is a log message\r\n") 211 message.TypeReturns("OUT") 212 message.TimestampReturns(time.Unix(1468969692, 0)) // "2016-07-19T16:08:12-07:00" 213 message.SourceTypeReturns("APP/PROC/WEB") 214 message.SourceInstanceReturns("12") 215 }) 216 217 Context("with header", func() { 218 Context("single line log message", func() { 219 It("prints out a single line to STDOUT", func() { 220 ui.DisplayLogMessage(message, true) 221 Expect(ui.Out).To(Say("2016-07-19T16:08:12.00-0700 \\[APP/PROC/WEB/12\\] OUT This is a log message\n")) 222 }) 223 }) 224 225 Context("multi-line log message", func() { 226 BeforeEach(func() { 227 var err error 228 ui.TimezoneLocation, err = time.LoadLocation("America/Los_Angeles") 229 Expect(err).NotTo(HaveOccurred()) 230 231 message.MessageReturns("This is a log message\nThis is also a log message") 232 }) 233 234 It("prints out mutliple lines to STDOUT", func() { 235 ui.DisplayLogMessage(message, true) 236 Expect(ui.Out).To(Say("2016-07-19T16:08:12.00-0700 \\[APP/PROC/WEB/12\\] OUT This is a log message\n")) 237 Expect(ui.Out).To(Say("2016-07-19T16:08:12.00-0700 \\[APP/PROC/WEB/12\\] OUT This is also a log message\n")) 238 }) 239 }) 240 }) 241 242 Context("without header", func() { 243 Context("single line log message", func() { 244 It("prints out a single line to STDOUT", func() { 245 ui.DisplayLogMessage(message, false) 246 Expect(ui.Out).To(Say("This is a log message\n")) 247 }) 248 }) 249 250 Context("multi-line log message", func() { 251 BeforeEach(func() { 252 var err error 253 ui.TimezoneLocation, err = time.LoadLocation("America/Los_Angeles") 254 Expect(err).NotTo(HaveOccurred()) 255 256 message.MessageReturns("This is a log message\nThis is also a log message") 257 }) 258 259 It("prints out mutliple lines to STDOUT", func() { 260 ui.DisplayLogMessage(message, false) 261 Expect(ui.Out).To(Say("This is a log message\n")) 262 Expect(ui.Out).To(Say("This is also a log message\n")) 263 }) 264 }) 265 }) 266 267 Context("error log lines", func() { 268 BeforeEach(func() { 269 message.TypeReturns("ERR") 270 }) 271 It("colors the line red", func() { 272 ui.DisplayLogMessage(message, false) 273 Expect(ui.Out).To(Say("\x1b\\[31mThis is a log message\x1b\\[0m\n")) 274 }) 275 }) 276 }) 277 278 Describe("DisplayNewline", func() { 279 It("displays a new line", func() { 280 ui.DisplayNewline() 281 Expect(ui.Out).To(Say("\n")) 282 }) 283 }) 284 285 Describe("DisplayOK", func() { 286 It("displays 'OK' in green and bold", func() { 287 ui.DisplayOK() 288 Expect(ui.Out).To(Say("\x1b\\[32;1mOK\x1b\\[0m")) 289 }) 290 }) 291 292 Describe("DisplayTableWithHeader", func() { 293 It("makes the first row bold", func() { 294 ui.DisplayTableWithHeader(" ", 295 [][]string{ 296 {"", "header1", "header2", "header3"}, 297 {"#0", "data1", "data2", "data3"}, 298 }, 299 2) 300 Expect(ui.Out).To(Say(" \x1b\\[1mheader1\x1b\\[0m")) // Makes sure empty values are not bolded 301 Expect(ui.Out).To(Say("\x1b\\[1mheader2\x1b\\[0m")) 302 Expect(ui.Out).To(Say("\x1b\\[1mheader3\x1b\\[0m")) 303 Expect(ui.Out).To(Say("#0 data1 data2 data3")) 304 }) 305 }) 306 307 // Covers the happy paths, additional cases are tested in TranslateText 308 Describe("DisplayText", func() { 309 It("displays the template with map values substituted in to ui.Out with a newline", func() { 310 ui.DisplayText( 311 "template with {{.SomeMapValue}}", 312 map[string]interface{}{ 313 "SomeMapValue": "map-value", 314 }) 315 Expect(ui.Out).To(Say("template with map-value\n")) 316 }) 317 318 Context("when the locale is not set to english", func() { 319 BeforeEach(func() { 320 fakeConfig.LocaleReturns("fr-FR") 321 322 var err error 323 ui, err = NewUI(fakeConfig) 324 Expect(err).NotTo(HaveOccurred()) 325 326 ui.Out = NewBuffer() 327 }) 328 329 It("displays the translated template with map values substituted in to ui.Out", func() { 330 ui.DisplayText( 331 "\nTIP: Use '{{.Command}}' to target new org", 332 map[string]interface{}{ 333 "Command": "foo", 334 }) 335 Expect(ui.Out).To(Say("\nASTUCE : utilisez 'foo' pour cibler une nouvelle organisation")) 336 }) 337 }) 338 }) 339 340 Describe("DisplayTextWithFlavor", func() { 341 It("displays the template to ui.Out", func() { 342 ui.DisplayTextWithFlavor("some-template") 343 Expect(ui.Out).To(Say("some-template")) 344 }) 345 346 Context("when an optional map is passed in", func() { 347 It("displays the template with map values colorized, bolded, and substituted in to ui.Out", func() { 348 ui.DisplayTextWithFlavor( 349 "template with {{.SomeMapValue}}", 350 map[string]interface{}{ 351 "SomeMapValue": "map-value", 352 }) 353 Expect(ui.Out).To(Say("template with \x1b\\[36;1mmap-value\x1b\\[0m")) 354 }) 355 }) 356 357 Context("when multiple optional maps are passed in", func() { 358 It("displays the template with only the first map values colorized, bolded, and substituted in to ui.Out", func() { 359 ui.DisplayTextWithFlavor( 360 "template with {{.SomeMapValue}} and {{.SomeOtherMapValue}}", 361 map[string]interface{}{ 362 "SomeMapValue": "map-value", 363 }, 364 map[string]interface{}{ 365 "SomeOtherMapValue": "other-map-value", 366 }) 367 Expect(ui.Out).To(Say("template with \x1b\\[36;1mmap-value\x1b\\[0m and <no value>")) 368 }) 369 }) 370 371 Context("when the locale is not set to english", func() { 372 BeforeEach(func() { 373 fakeConfig.LocaleReturns("fr-FR") 374 375 var err error 376 ui, err = NewUI(fakeConfig) 377 Expect(err).NotTo(HaveOccurred()) 378 379 ui.Out = NewBuffer() 380 }) 381 382 It("displays the translated template with map values colorized, bolded and substituted in to ui.Out", func() { 383 ui.DisplayTextWithFlavor( 384 "App {{.AppName}} does not exist.", 385 map[string]interface{}{ 386 "AppName": "some-app-name", 387 }) 388 Expect(ui.Out).To(Say("L'application \x1b\\[36;1msome-app-name\x1b\\[0m n'existe pas.\n")) 389 }) 390 }) 391 }) 392 393 Describe("DisplayTextWithBold", func() { 394 It("displays the template to ui.Out", func() { 395 ui.DisplayTextWithBold("some-template") 396 Expect(ui.Out).To(Say("some-template")) 397 }) 398 399 Context("when an optional map is passed in", func() { 400 It("displays the template with map values bolded and substituted in to ui.Out", func() { 401 ui.DisplayTextWithBold( 402 "template with {{.SomeMapValue}}", 403 map[string]interface{}{ 404 "SomeMapValue": "map-value", 405 }) 406 Expect(ui.Out).To(Say("template with \x1b\\[1mmap-value\x1b\\[0m")) 407 }) 408 }) 409 410 Context("when multiple optional maps are passed in", func() { 411 It("displays the template with only the first map values bolded and substituted in to ui.Out", func() { 412 ui.DisplayTextWithBold( 413 "template with {{.SomeMapValue}} and {{.SomeOtherMapValue}}", 414 map[string]interface{}{ 415 "SomeMapValue": "map-value", 416 }, 417 map[string]interface{}{ 418 "SomeOtherMapValue": "other-map-value", 419 }) 420 Expect(ui.Out).To(Say("template with \x1b\\[1mmap-value\x1b\\[0m and <no value>")) 421 }) 422 }) 423 424 Context("when the locale is not set to english", func() { 425 BeforeEach(func() { 426 fakeConfig.LocaleReturns("fr-FR") 427 428 var err error 429 ui, err = NewUI(fakeConfig) 430 Expect(err).NotTo(HaveOccurred()) 431 432 ui.Out = NewBuffer() 433 }) 434 435 It("displays the translated template with map values bolded and substituted in to ui.Out", func() { 436 ui.DisplayTextWithBold( 437 "App {{.AppName}} does not exist.", 438 map[string]interface{}{ 439 "AppName": "some-app-name", 440 }) 441 Expect(ui.Out).To(Say("L'application \x1b\\[1msome-app-name\x1b\\[0m n'existe pas.\n")) 442 }) 443 }) 444 }) 445 446 // Covers the happy paths, additional cases are tested in TranslateText 447 Describe("DisplayWarning", func() { 448 It("displays the warning to ui.Err", func() { 449 ui.DisplayWarning( 450 "template with {{.SomeMapValue}}", 451 map[string]interface{}{ 452 "SomeMapValue": "map-value", 453 }) 454 Expect(ui.Err).To(Say("template with map-value")) 455 }) 456 457 Context("when the locale is not set to english", func() { 458 BeforeEach(func() { 459 fakeConfig.LocaleReturns("fr-FR") 460 461 var err error 462 ui, err = NewUI(fakeConfig) 463 Expect(err).NotTo(HaveOccurred()) 464 465 ui.Err = NewBuffer() 466 }) 467 468 It("displays the translated warning to ui.Err", func() { 469 ui.DisplayWarning( 470 "'{{.VersionShort}}' and '{{.VersionLong}}' are also accepted.", 471 map[string]interface{}{ 472 "VersionShort": "some-value", 473 "VersionLong": "some-other-value", 474 }) 475 Expect(ui.Err).To(Say("'some-value' et 'some-other-value' sont également acceptés.\n")) 476 }) 477 }) 478 }) 479 480 // Covers the happy paths, additional cases are tested in TranslateText 481 Describe("DisplayWarnings", func() { 482 It("displays the warnings to ui.Err", func() { 483 ui.DisplayWarnings([]string{"warning-1", "warning-2"}) 484 Expect(ui.Err).To(Say("warning-1\n")) 485 Expect(ui.Err).To(Say("warning-2\n")) 486 }) 487 488 Context("when the locale is not set to english", func() { 489 BeforeEach(func() { 490 fakeConfig.LocaleReturns("fr-FR") 491 492 var err error 493 ui, err = NewUI(fakeConfig) 494 Expect(err).NotTo(HaveOccurred()) 495 496 ui.Err = NewBuffer() 497 }) 498 499 It("displays the translated warnings to ui.Err", func() { 500 ui.DisplayWarnings([]string{"Also delete any mapped routes", "FEATURE FLAGS"}) 501 Expect(ui.Err).To(Say("Supprimer aussi les routes mappées\n")) 502 Expect(ui.Err).To(Say("INDICATEURS DE FONCTION\n")) 503 }) 504 }) 505 }) 506 507 Describe("RequestLoggerFileWriter", func() { 508 It("returns a RequestLoggerFileWriter with the consistent filewriting mutex", func() { 509 logger1 := ui.RequestLoggerFileWriter(nil) 510 logger2 := ui.RequestLoggerFileWriter(nil) 511 512 c := make(chan bool) 513 err := logger1.Start() 514 Expect(err).ToNot(HaveOccurred()) 515 go func() { 516 Expect(logger2.Start()).ToNot(HaveOccurred()) 517 c <- true 518 }() 519 Consistently(c).ShouldNot(Receive()) 520 Expect(logger1.Stop()).ToNot(HaveOccurred()) 521 Eventually(c).Should(Receive()) 522 }) 523 }) 524 525 Describe("RequestLoggerTerminalDisplay", func() { 526 It("returns a RequestLoggerTerminalDisplay with the consistent display mutex", func() { 527 logger1 := ui.RequestLoggerTerminalDisplay() 528 logger2 := ui.RequestLoggerTerminalDisplay() 529 530 c := make(chan bool) 531 err := logger1.Start() 532 Expect(err).ToNot(HaveOccurred()) 533 go func() { 534 Expect(logger2.Start()).ToNot(HaveOccurred()) 535 c <- true 536 }() 537 Consistently(c).ShouldNot(Receive()) 538 Expect(logger1.Stop()).ToNot(HaveOccurred()) 539 Eventually(c).Should(Receive()) 540 }) 541 }) 542 543 Describe("TranslateText", func() { 544 It("returns the template", func() { 545 Expect(ui.TranslateText("some-template")).To(Equal("some-template")) 546 }) 547 548 Context("when an optional map is passed in", func() { 549 It("returns the template with map values substituted in", func() { 550 expected := ui.TranslateText( 551 "template {{.SomeMapValue}}", 552 map[string]interface{}{ 553 "SomeMapValue": "map-value", 554 }) 555 Expect(expected).To(Equal("template map-value")) 556 }) 557 }) 558 559 Context("when multiple optional maps are passed in", func() { 560 It("returns the template with only the first map values substituted in", func() { 561 expected := ui.TranslateText( 562 "template with {{.SomeMapValue}} and {{.SomeOtherMapValue}}", 563 map[string]interface{}{ 564 "SomeMapValue": "map-value", 565 }, 566 map[string]interface{}{ 567 "SomeOtherMapValue": "other-map-value", 568 }) 569 Expect(expected).To(Equal("template with map-value and <no value>")) 570 }) 571 }) 572 573 Context("when the locale is not set to english", func() { 574 BeforeEach(func() { 575 fakeConfig.LocaleReturns("fr-FR") 576 577 var err error 578 ui, err = NewUI(fakeConfig) 579 Expect(err).NotTo(HaveOccurred()) 580 }) 581 582 It("returns the translated template", func() { 583 expected := ui.TranslateText(" View allowable quotas with 'CF_NAME quotas'") 584 Expect(expected).To(Equal(" Affichez les quotas pouvant être alloués avec 'CF_NAME quotas'")) 585 }) 586 }) 587 }) 588 589 Describe("UserFriendlyDate", func() { 590 It("formats a time into an ISO8601 string", func() { 591 Expect(ui.UserFriendlyDate(time.Unix(0, 0))).To(MatchRegexp("\\w{3} [0-3]\\d \\w{3} [0-2]\\d:[0-5]\\d:[0-5]\\d \\w+ \\d{4}")) 592 }) 593 }) 594 })