spec/lib/gdk/output_spec.rb (395 lines of code) (raw):

# frozen_string_literal: true RSpec.describe GDK::Output do describe '.ensure_utf8' do context 'when input is nil' do it 'returns an empty string' do expect(described_class.ensure_utf8(nil)).to eq('') end end context 'when input is not a string' do it 'returns the input unchanged' do expect(described_class.ensure_utf8(123)).to eq(123) end end context 'when input is already UTF-8' do it 'returns the string unchanged' do expect(described_class.ensure_utf8('abcde')).to eq('abcde') end end context 'when input is not a UTF-8 string' do it 'encodes the string to UTF-8' do ascii_string = 'abcde'.encode('ASCII-8BIT') expect(described_class.ensure_utf8(ascii_string).encoding).to eq(Encoding::UTF_8) end end context 'when input is a frozen string' do it 'duplicates and encodes the string to UTF-8' do frozen_string = 'abcde'.encode('ASCII-8BIT').freeze result = described_class.ensure_utf8(frozen_string) expect(result.encoding).to eq(Encoding::UTF_8) expect(result).not_to be_frozen end end end describe '.print' do context 'by default' do it 'prints to stdout' do expect { described_class.print('test') }.to output('test').to_stdout end end context 'with stderr: true' do it 'prints to stderr' do expect { described_class.print('test', stderr: true) }.to output('test').to_stderr end end context 'when the rake task logger is set' do let(:task) { instance_double(Rake::Task, name: 'test task') } before do logger = Support::Rake::TaskLogger.from_task(task) allow(logger).to receive(:create_latest_symlink!) Support::Rake::TaskLogger.set_current!(logger) end after do Support::Rake::TaskLogger.set_current!(nil) end context 'by default' do it 'prints to the file' do buffer = StringIO.new expect(Support::Rake::TaskLogger.current).to receive(:file).and_return(buffer) expect { described_class.print('test') }.not_to output.to_stdout end end context 'with stderr: true' do it 'prints to stderr' do buffer = StringIO.new expect(Support::Rake::TaskLogger.current).to receive(:file).and_return(buffer) expect { described_class.print('test', stderr: true) }.not_to output.to_stderr end end end end describe '.puts' do context 'by default' do it 'puts to stdout' do expect { described_class.puts('test') }.to output("test\n").to_stdout end end context 'with stderr: true' do it 'puts to stdout' do expect { described_class.puts('test', stderr: true) }.to output("test\n").to_stderr end end context 'when the rake task logger is set' do let(:task) { instance_double(Rake::Task, name: 'test task') } before do logger = Support::Rake::TaskLogger.from_task(task) allow(logger).to receive(:create_latest_symlink!) Support::Rake::TaskLogger.set_current!(logger) end after do Support::Rake::TaskLogger.set_current!(nil) end context 'by default' do it 'prints to the file' do buffer = StringIO.new expect(Support::Rake::TaskLogger.current).to receive(:file).and_return(buffer) expect { described_class.puts('test') }.not_to output.to_stdout end end context 'with stderr: true' do it 'prints to stderr' do buffer = StringIO.new expect(Support::Rake::TaskLogger.current).to receive(:file).and_return(buffer) expect { described_class.puts('test', stderr: true) }.not_to output.to_stderr end end end end describe '.notice' do it 'puts formatted message to stdout' do stub_no_color_env('') expect { described_class.notice('test') }.to output("=> test\n").to_stdout end end describe '.notice_format' do it 'returns formatted message' do expect(described_class.notice_format('test')).to eq('=> test') end end describe '.info' do it 'puts to stdout' do stub_no_color_env('') expect { described_class.info('test') }.to output("\u2139\ufe0f test\n").to_stdout end end describe '.success' do context "when we're not a tty" do it 'puts to stdout' do stub_tty(false) expect { described_class.success('test') }.to output("test\n").to_stdout end end context 'when we are a tty' do context 'when NO_COLOR=true is not defined' do it 'puts to stdout' do stub_tty(true) stub_no_color_env('') expect { described_class.success('test') }.to output("\u2705\ufe0f test\n").to_stdout end end context 'when NO_COLOR=true is defined' do it 'puts to stdout minus icon and colorization' do stub_no_color_env('true') expect { described_class.success('test') }.to output("test\n").to_stdout end end end end describe '.warn' do context "when we're not a tty" do it 'puts to stderr minus icon and colorization' do stub_tty(false) expect { described_class.warn('test') }.to output("WARNING: test\n").to_stderr end end context 'when we are a tty' do context 'when NO_COLOR=true is not defined' do it 'puts to stderr' do stub_no_color_env('') expect { described_class.warn('test') }.to output("\u26a0\ufe0f \e[33mWARNING\e[0m: test\n").to_stderr end end context 'when NO_COLOR=true is defined' do it 'puts to stderr minus icon and colorization' do stub_no_color_env('true') expect { described_class.warn('test') }.to output("WARNING: test\n").to_stderr end end end end describe '.debug' do before do stub_tty(false) stub_gdk_debug(debug_enabled) end context 'when debug is not enabled' do let(:debug_enabled) { false } it 'outputs nothing' do expect { described_class.debug('test') }.not_to output("DEBUG: test\n").to_stderr end end context 'when debug is enabled' do let(:debug_enabled) { true } context "when we're not a tty" do it 'puts to stderr minus icon and colorization' do expect { described_class.debug('test') }.to output("DEBUG: test\n").to_stderr end end context 'when we are a tty' do context 'when NO_COLOR=true is not defined' do it 'puts to stderr' do stub_no_color_env('') expect { described_class.debug('test') }.to output("\u26CF\ufe0f \e[34mDEBUG\e[0m: test\n").to_stderr end it 'returns a UTF-8 message that cleans up invalid characters' do stub_no_color_env('') msg = "🐤🐤🐤🐤\xF0\x9F\x90".dup.force_encoding('ASCII-8BIT') # rubocop:disable Performance/UnfreezeString -- This doesn't work with frozen_string_literal set expect { described_class.debug(msg) }.to output("\u26CF\ufe0f \e[34mDEBUG\e[0m: 🐤🐤🐤🐤�\n").to_stderr end end context 'when NO_COLOR=true is defined' do it 'puts to stderr minus icon and colorization' do stub_no_color_env('true') expect { described_class.debug('test') }.to output("DEBUG: test\n").to_stderr end end end end end describe '.format_error' do context "when we're not a tty" do it 'puts to stderr minus icon and colorization' do stub_tty(false) expect(described_class.format_error('test')).to eq('ERROR: test') end end context 'when we are a tty' do context 'when NO_COLOR=true is not defined' do it 'puts to stderr' do stub_no_color_env('') expect(described_class.format_error('test')).to eq("\u274C\ufe0f \e[31mERROR\e[0m: test") end end context 'when NO_COLOR=true is defined' do it 'puts to stderr minus icon and colorization' do stub_no_color_env('true') expect(described_class.format_error('test')).to eq('ERROR: test') end end end end describe '.error' do context "when we're not a tty" do it 'puts to stderr minus icon and colorization' do stub_tty(false) expect { described_class.error('test') }.to output("ERROR: test\n").to_stderr end end context 'when we are a tty' do context 'when NO_COLOR=true is not defined' do it 'puts to stderr' do stub_no_color_env('') expect { described_class.error('test') }.to output("\u274C\ufe0f \e[31mERROR\e[0m: test\n").to_stderr end end context 'when NO_COLOR=true is defined' do it 'puts to stderr minus icon and colorization' do stub_no_color_env('true') expect { described_class.error('test') }.to output("ERROR: test\n").to_stderr end end end it 'reports message to telemetry', :hide_stdout do described_class.error('test') expect(GDK::Telemetry).to have_received(:capture_exception).with('test') end it 'reports exception to telemetry', :hide_stdout do exception = StandardError.new described_class.error('test', exception) expect(GDK::Telemetry).to have_received(:capture_exception).with(exception) end it 'skips report if disabled', :hide_stdout do described_class.error('test', report_error: false) expect_no_error_report end end describe '.abort' do context "when we're not a tty" do it 'puts to stderr minus icon and colorization' do stub_tty(false) expect { described_class.abort('test') }.to raise_error(/test/).and output("ERROR: test\n").to_stderr end end context 'when we are a tty' do context 'when NO_COLOR=true is not defined' do it 'puts to stderr' do stub_no_color_env('') expect { described_class.abort('test') }.to raise_error(/test/).and output("\u274C\ufe0f \e[31mERROR\e[0m: test\n").to_stderr end end context 'when NO_COLOR=true is defined' do it 'puts to stderr minus icon and colorization' do stub_no_color_env('true') expect { described_class.abort('test') }.to raise_error(/test/).and output("ERROR: test\n").to_stderr end end end it 'reports message to telemetry' do message = 'test' expect { described_class.abort(message) } .to raise_error(SystemExit).and output.to_stderr expect(GDK::Telemetry).to have_received(:capture_exception).with(message) end it 'reports exception to telemetry' do message = 'test' exception = StandardError.new expect { described_class.abort(message, exception) } .to raise_error(SystemExit).and output.to_stderr expect(GDK::Telemetry).to have_received(:capture_exception).with(exception) end it 'skips report if disabled' do expect { described_class.abort('test', report_error: false) } .to raise_error(SystemExit).and output.to_stderr expect_no_error_report end end describe '.color' do it 'returns a color for index' do expect(described_class.color(0)).to eq('31') end end describe '.ansi' do it 'returns the ansi color code string' do expect(described_class.ansi('31')).to eq("\e[31m") end end describe '.reset_color' do it 'returns the ansi reset code string' do expect(described_class.reset_color).to eq("\e[0m") end end describe '.wrap_in_color' do it 'returns a message that is colorized' do stub_tty(true) msg = 'An error occurred' expect(described_class.wrap_in_color(msg, described_class::COLOR_CODE_RED)).to eq("\e[31m#{msg}\e[0m") end end describe '.icon' do context 'when NO_COLOR=true is not defined' do it 'returns the icon code with trailing space' do icon = described_class::ICONS[:success] stub_no_color_env('') expect(described_class.icon(:success)).to eq("#{icon} ") end end context 'when NO_COLOR=true is defined' do it 'returns an empty string' do stub_no_color_env('true') expect(described_class.icon('doesntmatter')).to be_empty end end end describe '.colorize?' do context 'when NO_COLOR=true is not defined' do it 'returns true' do stub_no_color_env('') expect(described_class.colorize?).to be(true) end end context 'when NO_COLOR=true is defined' do it 'returns false' do stub_no_color_env('true') expect(described_class.colorize?).to be(false) end end end describe '.interactive?' do context 'when we have a TTY' do it 'returns true' do stub_tty(true) expect(described_class.interactive?).to be(true) end end context "when we don't have a TTY" do it 'returns false' do stub_tty(false) expect(described_class.interactive?).to be(false) end end end describe '.prompt' do let(:message) { 'Are you sure? [y/N]' } context 'when we have a TTY' do before do stub_tty(true) end it 'returns user input' do response = 'n' expect(Kernel).to receive(:print).with("#{message}: ") expect($stdout).to receive(:flush).and_call_original expect($stdin).to receive_message_chain(:gets, :to_s, :chomp).and_return(response) expect(described_class.prompt(message)).to eq(response) end end context "when we don't have a TTY" do before do stub_tty(false) end it 'raises an error' do expect(Kernel).to receive(:print).with("#{message}: ") expect($stdout).to receive(:flush).and_call_original expect { described_class.prompt(message) }.to raise_error(RuntimeError, 'Interactive terminal not available, aborting.') end end end end