inspec/test/unit/ui_test.rb
Clinton Wolfe c0a46cf841
UI Object for for CLI work (#3618)
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
2018-12-12 11:44:16 -05:00

423 lines
No EOL
14 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# require 'helper'
require 'minitest'
require 'minitest/spec'
require 'inspec/ui'
require 'inspec/base_cli'
require 'inspec/errors'
require 'stringio'
# https://gist.github.com/chrisopedia/8754917
# http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
ANSI_CODES = {
reset: "\e[0m",
bold: "\e[1m",
color: {
red: "\e[38;5;9m", # 256-color light red
green: "\e[38;5;41m", # 256-color light green
yellow: "\e[33m",
cyan: "\e[36m",
white: "\e[37m",
grey: "\e[38;5;247m", # 256-color medium grey
},
}.freeze
GLYPHS = {
bullet: '•', # BULLET, Unicode: U+2022, UTF-8: E2 80 A2
check: '✔', # HEAVY CHECK MARK, Unicode: U+2714, UTF-8: E2 9C 94
swirl: '↺', # ANTICLOCKWISE OPEN CIRCLE ARROW, Unicode U+21BA, UTF-8: E2 86 BA
script_x: '×', # MULTIPLICATION SIGN, Unicode: U+00D7, UTF-8: C3 97
question: '?', # normal ASCII question mark
em_dash: '─', # BOX DRAWINGS LIGHT HORIZONTAL Unicode: U+2500, UTF-8: E2 94 80
heavy_dash: '≖', # RING IN EQUAL TO, Unicode: U+2256, UTF-8: E2 89 96
vertical_dash: '│', # │ BOX DRAWINGS LIGHT VERTICAL, Unicode: U+2502, UTF-8: E2 94 82
table_corner: '⨀', # N-ARY CIRCLED DOT OPERATOR, Unicode: U+2A00, UTF-8: E2 A8 80
}.freeze
#=============================================================================#
# Low-Level Formatting
#=============================================================================#
describe 'Inspec::UI low-level Formatting' do
let(:fixture_io) { StringIO.new() }
let(:output) { fixture_io.string }
let(:ui) { Inspec::UI.new(io: fixture_io) }
describe 'plain' do
it 'uses no ANSI markers' do
ui.plain('test')
output.must_include('test')
output.wont_include('\e[')
end
end
describe 'when color is enabled' do
let(:ui) { Inspec::UI.new(color: true, io: fixture_io) }
describe 'bold' do
it 'uses ANSI bold markers' do
ui.bold('test')
output.must_equal(ANSI_CODES[:bold] + 'test' + ANSI_CODES[:reset])
end
end
describe 'colors' do
[:red, :green, :cyan, :yellow, :white, :grey].each do |color|
it ('uses the color code for ' + color.to_s) do
ui.send(color, 'test')
output.must_equal(ANSI_CODES[:color][color] + 'test' + ANSI_CODES[:reset])
end
end
end
end
describe 'when color is disabled' do
let(:ui) { Inspec::UI.new(color: false, io: fixture_io) }
describe 'bold' do
it 'uses no ANSI codes' do
ui.bold('test')
output.wont_include('\e[')
output.must_equal('test')
end
end
describe 'colors' do
[:red, :green, :yellow, :white, :grey].each do |color|
it ('uses no ANSI codes for ' + color.to_s) do
ui.send(color, 'test')
output.wont_include('\e[')
output.must_equal('test')
end
end
end
end
end
#=============================================================================#
# High-Level Formatting
#=============================================================================#
describe 'Inspec::UI High-Level Formatting' do
let(:fixture_io) { StringIO.new() }
let(:output) { fixture_io.string }
describe 'when color is enabled' do
let(:ui) { Inspec::UI.new(color: true, io: fixture_io) }
describe 'emphasis' do
it 'uses ANSI escapes' do
ui.emphasis('test')
output.must_equal(ANSI_CODES[:color][:cyan] + 'test' + ANSI_CODES[:reset])
end
end
describe 'headline' do
it 'formats the headline when short' do
ui.headline('test')
output.must_match(/^\n/) # Start with one newlines
expected = ''
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + 'test' + ANSI_CODES[:reset]
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
output.must_include(expected)
output.must_match(/\n\n$/) # End with two newline
end
it 'formats the headline when longer' do
ui.headline('Testing is Such a Pleasure!')
expected = ''
expected += ' ' + GLYPHS[:em_dash] * 24 + ' '
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + 'Testing is Such a Pleasure!' + ANSI_CODES[:reset]
expected += ' ' + GLYPHS[:em_dash] * 24 + ' '
output.must_include(expected)
end
end
describe 'error' do
it 'formats the message' do
ui.error('Everything has gone terribly wrong')
expected = ''
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:red]
expected += 'ERROR:'
expected += ANSI_CODES[:reset]
expected += ' '
expected += 'Everything has gone terribly wrong'
output.must_include(expected)
output.must_match(/\n$/) # End with a newline
end
end
describe 'warning' do
it 'formats the message' do
ui.warning('Maybe we can still pull through this')
expected = ''
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:yellow]
expected += 'WARNING:'
expected += ANSI_CODES[:reset]
expected += ' '
expected += 'Maybe we can still pull through this'
output.must_include(expected)
output.must_match(/\n$/) # End with a newline
end
end
end
describe 'when color is disabled' do
let(:ui) { Inspec::UI.new(color: false, io: fixture_io) }
describe 'emphasis' do
it 'does not use ANSI escapes' do
ui.emphasis('test')
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
output.must_equal('test')
end
end
describe 'headline' do
it 'formats the headline when short' do
ui.headline('test')
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
expected = ''
expected += ' ' + '-' * 36 + ' '
expected += 'test'
expected += ' ' + '-' * 36 + ' '
output.must_include(expected)
end
end
describe 'error' do
it 'formats the message without color' do
ui.error('Everything has gone terribly wrong')
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
expected = ''
expected += 'ERROR:'
expected += ' '
expected += 'Everything has gone terribly wrong'
output.must_include(expected)
output.must_match(/\n$/) # End with a newline
end
end
describe 'warning' do
it 'formats the message' do
ui.warning('Maybe we can still pull through this')
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
expected = ''
expected += 'WARNING:'
expected += ' '
expected += 'Maybe we can still pull through this'
output.must_include(expected)
output.must_match(/\n$/) # End with a newline
end
end
end
end
#=============================================================================#
# Tables and Lists
#=============================================================================#
describe 'Inspec::UI Tables and Lists' do
let(:fixture_io) { StringIO.new() }
let(:output) { fixture_io.string }
describe 'when color is enabled' do
let(:ui) { Inspec::UI.new(color: true, io: fixture_io) }
describe('line') do
it 'draws a line' do
ui.line
expected = ANSI_CODES[:bold] + GLYPHS[:heavy_dash]*80 + ANSI_CODES[:reset] + "\n"
output.must_equal(expected)
end
end
describe('list_item') do
it 'makes a bullet point' do
ui.list_item('test')
expected = ' '
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white]
expected += GLYPHS[:bullet]
expected += ANSI_CODES[:reset]
expected += ' ' + 'test' + "\n"
output.must_equal(expected)
end
end
describe('tables') do
it 'makes a table' do
ui.table do |t|
t.header = ['Fruit', 'Tartness', 'Succulence']
t << ['Dragonfruit', 'Very Low', 'High']
t << ["The Exquisite Lime, Scurvy's Bane", 'High', 'Medium']
end
lines = output.split("\n")
# First, third, and last lines should be horizontal dividors
[0, 2, -1].each do |idx|
lines[idx].must_include(GLYPHS[:em_dash]*3)
lines[idx].wont_include(' ')
end
# Second, fourth, and fifth lines should have custom vertical dividors
[1, 3, 4].each do |idx|
lines[idx].must_match(/^#{GLYPHS[:vertical_dash]}/) # Start with a vertical line
lines[idx].must_match(/#{GLYPHS[:vertical_dash]}$/) # End with a vertical line
end
# Second (header) line should have bold and white on each header label
lines[1].split(GLYPHS[:vertical_dash]).map(&:strip).reject{ |e| e == ""}.each do |header_label|
header_label.must_include ANSI_CODES[:bold] + ANSI_CODES[:color][:white]
header_label.must_include ANSI_CODES[:reset]
end
end
end
end
describe 'when color is disabled' do
let(:ui) { Inspec::UI.new(color: false, io: fixture_io) }
describe('line') do
it 'draws a line without ANSI codes or special glyphs' do
ui.line
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
expected = '-' * 80 + "\n"
output.must_equal(expected)
end
end
describe('list_item') do
it 'makes a bullet point without ANSI codes or special glyphs' do
ui.list_item('test')
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
expected = ' ' + '*' + ' ' + 'test' + "\n"
output.must_equal(expected)
end
end
describe('tables') do
it 'makes a table ANSI codes or special glyphs' do
ui.table do |t|
t.header = ['Fruit', 'Tartness', 'Succulence']
t << ['Dragonfruit', 'Very Low', 'High']
t << ["The Exquisite Lime, Scurvy's Bane", 'High', 'Medium']
end
output.wont_include('\e[') # No ANSI escapes
output.wont_match(/[^[:ascii:]]/) # No non-ASCII chars (such as UTF-8 glyphs)
lines = output.split("\n")
# First, third, and last lines should be horizontal dividors
[0, 2, -1].each do |idx|
lines[idx].must_match(/^\+/) # Start with a corner
lines[idx].must_match(/\+$/) # End with a corner
lines[idx].must_match(/\-\+\-/) # Have internal corners
lines[idx].wont_include(' ')
end
# Second, fourth, and fifth lines should have stock vertical dividors
[1, 3, 4].each do |idx|
lines[idx].must_match(/^\|/) # Start with a vertical line
lines[idx].must_match(/\|$/) # End with a vertical line
lines[idx].must_match(/\s\|\s/) # Have vertical line
lines[idx].wont_include('+')
end
end
end
end
end
#=============================================================================#
# CLI Integration
#=============================================================================#
describe 'Inspec::UI CLI integration' do
let(:fixture_io) { StringIO.new() }
let(:output) { fixture_io.string }
let(:cli) { Inspec::BaseCLI.new }
describe 'ui method' do
it 'should respond to ui' do
cli.must_respond_to(:ui)
cli.must_respond_to(:'ui=')
end
end
describe 'backwards compatibility' do
it 'should support plain_text' do
cli.ui = Inspec::UI.new(io: fixture_io)
cli.plain_text('test')
output.must_equal "test\n"
end
it 'should support mark_text' do
# mark_text applies cyan and DOES NOT PRINT
cli.ui = Inspec::UI.new(io: fixture_io)
result = cli.mark_text('test')
result.must_equal ANSI_CODES[:color][:cyan] + 'test' + ANSI_CODES[:reset]
output.must_equal ''
end
it 'should support headline' do
cli.ui = Inspec::UI.new(io: fixture_io)
cli.headline('test')
output.must_match(/^\n/) # Start with one newlines
expected = ''
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + 'test' + ANSI_CODES[:reset]
expected += ' ' + GLYPHS[:em_dash] * 36 + ' '
output.must_include(expected)
output.must_match(/\n\n$/) # End with two newline
end
it 'should support li' do
cli.ui = Inspec::UI.new(io: fixture_io)
cli.li('test')
expected = ' '
expected += ANSI_CODES[:bold] + ANSI_CODES[:color][:white]
expected += GLYPHS[:bullet]
expected += ANSI_CODES[:reset]
expected += ' ' + 'test' + "\n"
output.must_equal(expected)
end
end
end
#=============================================================================#
# Interactivity
#=============================================================================#
describe 'interactivity' do
describe 'when interactivity is disabled' do
describe 'interactive check' do
it "should be false" do
ui = Inspec::UI.new(interactive: false)
ui.interactive?.must_equal false
end
end
describe 'prompt' do
it 'should throw an exception if interactivity is disabled' do
ui = Inspec::UI.new(interactive: false)
->() { ui.prompt }.must_raise Inspec::UserInteractionRequired
end
end
end
end
#=============================================================================#
# Exit Codes
#=============================================================================#
# These are tested in functional tests
describe 'Inspec UI Exit Codes' do
[
:EXIT_NORMAL,
:EXIT_USAGE_ERROR,
:EXIT_PLUGIN_ERROR,
:EXIT_SKIPPED_TESTS,
:EXIT_FAILED_TESTS,
].each do |const_name|
it "should define #{const_name}" do
Inspec::UI.const_defined?(const_name).must_equal true
end
end
end