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