diff --git a/Gemfile.lock b/Gemfile.lock index 8aaef74..fed40a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: simplecov-inline (0.1.0) + rainbow (~> 3.1) simplecov (~> 0.22) GEM diff --git a/lib/simplecov/inline.rb b/lib/simplecov/inline.rb index 51a8089..71cbb1f 100644 --- a/lib/simplecov/inline.rb +++ b/lib/simplecov/inline.rb @@ -1,4 +1,5 @@ require_relative 'inline/version' +require_relative 'inline/formatter' module SimpleCov module Inline diff --git a/lib/simplecov/inline/formatter.rb b/lib/simplecov/inline/formatter.rb new file mode 100644 index 0000000..7eca6a5 --- /dev/null +++ b/lib/simplecov/inline/formatter.rb @@ -0,0 +1,101 @@ +require 'rainbow' + +module Simplecov + module Inline + class Formatter + Result = Struct.new(:file, :start_line, :end_line, :type) do + def to_s + lines = [start_line, end_line].uniq.join('-') + "#{file}:#{lines} (#{type})" + end + end + + Config = Struct.new(:files) do + def no_output!(reason:) + @no_output = reason + end + + attr_reader :no_output + end + + def self.reset_config = @config = Config.new + reset_config + + def self.config(&block) + return @config if block.nil? + + block.call(@config) + + return if @config.files.nil? + + @config.files = @config.files.to_set.freeze + end + + def format(result, putter: Kernel) + missing_coverage = process_files(filtered_files(result:).reject { |file| fully_covered?(file) }) + success_message = build_success_message(missing_coverage:) + + if success_message + putter.puts success_message + return + end + + putter.puts + putter.puts Rainbow('Missing coverage:').yellow + putter.puts Rainbow(missing_coverage).yellow + putter.puts + end + + private + + def build_success_message(missing_coverage:) + if self.class.config.no_output + return Rainbow("Coverage output skipped. Reason: #{self.class.config.no_output}.").yellow + end + + return Rainbow("All branches covered (#{human_filter} files) ✔ ").green if missing_coverage.empty? + + nil + end + + def fully_covered?(file) + [file.covered_percent, file.branches_coverage_percent].all? { |coverage| coverage == 100 } + end + + def human_filter + self.class.config.files&.length || 'all' + end + + def filtered_files(result:) + filter = self.class.config.files + + return result.files if filter.nil? + + result.files.filter { |file| filter.include?(file.filename) } + end + + def process_files(files) + files.map do |file| + [ + build_line_coverage(file), + build_branch_coverage(file), + ] + end.flatten.compact.join("\n") + end + + def build_line_coverage(file) + file.missed_lines.map do |uncovered| + # uncovered is a SimpleCov::SourceFile::Line + Result.new(file.project_filename, uncovered.line_number, uncovered.line_number, 'line') + end + end + + def build_branch_coverage(file) + file.missed_branches.map do |uncovered| + # uncovered is a SimpleCov::SourceFile::Branch + Result.new(file.project_filename, uncovered.report_line, uncovered.report_line, "branch:#{uncovered.type}") + end + end + end + end +end diff --git a/simplecov-inline.gemspec b/simplecov-inline.gemspec index 826f348..bc12038 100644 --- a/simplecov-inline.gemspec +++ b/simplecov-inline.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + spec.add_dependency 'rainbow', '~> 3.1' spec.add_dependency 'simplecov', '~> 0.22' # For more information and examples about making a new gem, check out our diff --git a/spec/fixtures/file1.rb b/spec/fixtures/file1.rb new file mode 100644 index 0000000..56fb7f3 --- /dev/null +++ b/spec/fixtures/file1.rb @@ -0,0 +1,5 @@ +class File1 + def method(arg) + arg ? 1 : 2 + end +end diff --git a/spec/fixtures/file2.rb b/spec/fixtures/file2.rb new file mode 100644 index 0000000..691f2aa --- /dev/null +++ b/spec/fixtures/file2.rb @@ -0,0 +1,5 @@ +class File2 + def method(arg) + arg ? 1 : 2 + end +end diff --git a/spec/lib/simplecov/inline/formatter_spec.rb b/spec/lib/simplecov/inline/formatter_spec.rb new file mode 100644 index 0000000..a574b15 --- /dev/null +++ b/spec/lib/simplecov/inline/formatter_spec.rb @@ -0,0 +1,101 @@ +RSpec.describe Simplecov::Inline::Formatter do + after { described_class.reset_config } + + describe '#format' do + subject { described_class.new.format(result, putter:) } + + let(:putter) { class_double(Kernel) } + + before { allow(putter).to receive(:puts) } + + context 'missing coverage' do + let(:result) do + SimpleCov::Result.new({ + File.join(fixture_directory, 'file1.rb') => { + # 0 means line 3 is uncovered + 'lines' => [1, 1, 0, 1, 1], + # => 0 means the else branch is uncovered. + # 2 is an idenditifier, 3 is the start line, 12 is the start col, 3 is the end line and 14 is the end col. + 'branches' => {[:if, 0, 3, 4, 3, 8] => {[:then, 1, 3, 8, 3, 10] => 1, [:else, 2, 3, 12, 3, 14] => 0}}, + }, + }) + end + + it 'prints a yellow message saying coverage is missing' do + subject + + expect(putter).to have_received(:puts).with(Rainbow('Missing coverage:').yellow) + end + + it 'prints the missing lines and branches in yellow' do + subject + + expect(putter).to have_received(:puts).with(Rainbow([ + '/spec/fixtures/file1.rb:3 (line)', + '/spec/fixtures/file1.rb:3 (branch:else)', + ].join("\n")).yellow) + end + end + + context 'fully covered' do + let(:result) do + SimpleCov::Result.new({ + File.join(fixture_directory, 'file1.rb') => { + 'lines' => [1, 1, 1, 1, 1], + 'branches' => {[:if, 0, 3, 4, 3, 8] => {[:then, 1, 3, 8, 3, 10] => 1, [:else, 2, 3, 12, 3, 14] => 1}}, + }, + }) + end + + it 'prints a green message saying fully covered' do + subject + expect(putter).to have_received(:puts).with(Rainbow('All branches covered (all files) ✔ ').green) + end + end + + context 'output disabled' do + let(:result) { SimpleCov::Result.new({}) } + + before do + described_class.config { |config| config.no_output!(reason: 'no output thanks') } + end + + it 'prints a yellow message saying output is disabled' do + subject + expect(putter).to have_received(:puts).with(Rainbow('Coverage output skipped. Reason: no output thanks.').yellow) + end + end + + context 'filtering out of uncovered files' do + let(:result) do + SimpleCov::Result.new({ + File.join(fixture_directory, 'file1.rb') => {'lines' => [1, 1, 1, 1, 1]}, + File.join(fixture_directory, 'file2.rb') => {'lines' => [1, 1, 0, 1, 1]}, + }) + end + + before do + described_class.config { |config| config.files = [File.join(fixture_directory, 'file1.rb')]} + end + + it 'prints a green message saying fully covered, but notes only 1 file was considered' do + subject + expect(putter).to have_received(:puts).with(Rainbow('All branches covered (1 files) ✔ ').green) + end + end + end + + describe '.config' do + subject { described_class.config } + + it { is_expected.to have_attributes files: nil } + it { is_expected.to respond_to :no_output! } + + it 'can change the files' do + expect { described_class.config { |config| config.files = ['test.txt'] } } + .to change { described_class.config.files } + .from(nil) + .to(['test.txt']) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bf5ce84..37545e9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,3 +20,5 @@ c.syntax = :expect end end + +def fixture_directory = @fixture_directory ||= File.join(File.dirname(__FILE__), 'fixtures')