Skip to content

Instantly share code, notes, and snippets.

@georgyangelov
Created May 31, 2015 17:42
Show Gist options
  • Save georgyangelov/d7dbd5f1388e4f54ea7d to your computer and use it in GitHub Desktop.
Save georgyangelov/d7dbd5f1388e4f54ea7d to your computer and use it in GitHub Desktop.
RSpec nested formatter
RSpec::Support.require_rspec_core 'formatters/base_text_formatter'
class RspecFormatter < RSpec::Core::Formatters::BaseTextFormatter
RSpec::Core::Formatters.register self, :start,
:stop,
:example_group_started,
:example_group_finished,
:example_passed,
:example_failed,
:example_pending
def initialize(output)
super
@out = OutputManager.new(output)
@root = OutputGroup.new
@current = @root
@current_visible = @current
end
def start(_notification)
@out.new_line
@out.line 'Examples:'
@out.new_line
end
def stop(_notification)
@out.new_line
end
def example_group_started(notification)
new_group = @current.push_group(notification.group)
if @current.expanded
@out.indent
@out.new_line
@out.line new_group.heading
@current_visible = new_group
end
@current = new_group
end
def example_group_finished(_notification)
if @current == @current_visible
@out.line @current.heading(true) unless @current.expanded
@out.dedent
@current_visible = @current_visible.parent
end
@current = @current.parent
end
def example_passed(passed)
new_example = @current.push_example(passed.example, :success)
if @current.expanded
@out.indent
@out.new_line
@out.line new_example.heading
@out.dedent
else
@out.line @current_visible.heading
end
end
def example_failed(failed)
expand_current unless @current.expanded
new_example = @current.push_example(failed.example, :failure)
@out.indent
@out.new_line
@out.line new_example.heading
@out.dedent
end
def example_pending(pending)
expand_current unless @current.expanded
new_example = @current.push_example(pending.example, :pending)
@out.indent
@out.new_line
@out.line new_example.heading
@out.dedent
end
private
def expand_current
@current.expand_to_top
@current_visible.print @out, new_line: false
@current_visible = @current
@out.current_indent = @current_visible.indent
end
end
class OutputGroup
attr_reader :parent, :group, :children, :expanded
def initialize(parent = nil, group = nil)
@parent = parent
@group = group
@children = []
@expanded = !parent
end
def expand
@expanded = true
parent.expand
end
def heading(done = false)
if !@expanded && done
counter = RSpec::Core::Formatters::ConsoleCodes.wrap("[#{example_count}]", :success)
elsif !@expanded
counter = "[#{example_count}]"
end
"#{@group.description} #{counter}"
end
def indent
return 0 unless @parent
1 + @parent.indent
end
def print(out, new_line: true)
out.new_line if new_line
out.line heading(true)
if @expanded
out.indent
children.each { |child| child.print(out) }
out.dedent
end
end
def example_count
@children.map(&:example_count).reduce(:+) || 0
end
def push_group(group)
output_group = OutputGroup.new(self, group)
@children.push output_group
output_group
end
def push_example(example, status)
output_example = OutputExample.new(self, example, status)
@children.push output_example
output_example
end
def expand_to_top
@expanded = true
return self if @parent.expanded
@parent.expand_to_top
end
end
class OutputExample
attr_reader :parent, :example, :status
def initialize(parent, example, status)
@parent = parent
@example = example
@status = status
end
def heading
case @status
when :success then passed_output
when :pending then pending_output
when :failure then failure_output
end
end
def print(out)
out.new_line
out.line heading
end
def example_count
1
end
private
def passed_output
RSpec::Core::Formatters::ConsoleCodes.wrap(@example.description.strip, :success)
end
def pending_output
RSpec::Core::Formatters::ConsoleCodes.wrap("#{@example.description.strip} [PENDING]", :pending)
end
def failure_output
RSpec::Core::Formatters::ConsoleCodes.wrap("#{@example.description.strip} [FAILED]", :failure)
end
end
class OutputManager
attr_accessor :current_indent
def initialize(output)
@output = output
@current_indent = 0
end
def line(line)
@output.print "\r#{' ' * 70}\r#{indent_block(line)}"
flush
end
def append(string)
@output.print string
flush
end
def indent
@current_indent += 1
end
def dedent
@current_indent -= 1
end
def new_line(text = '')
@output.puts text
end
private
def flush
$stdout.flush
end
def indent_block(string)
string.lines.map { |line| "#{' ' * @current_indent}#{line}" }.join('')
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment