Class: Zenspec::Formatters::ProgressBarFormatter

Inherits:
RSpec::Core::Formatters::BaseFormatter
  • Object
show all
Defined in:
lib/zenspec/formatters/progress_bar_formatter.rb

Overview

Custom RSpec formatter that displays test execution with spinning animation, one line per file, and right-aligned progress.

Format while running: [spinner] filename --> test description [percentage current/total] Format when done: [icon] filename [percentage current/total]

Icons:

✔ - All tests passed (green)
✗ - Any test failed (red)
⊘ - Pending tests (cyan)
Spinner - Running (yellow): ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏

Examples:

Usage in .rspec file

--require zenspec/formatters/progress_bar_formatter
--format Zenspec::Formatters::ProgressBarFormatter

Command line usage

rspec --require zenspec/formatters/progress_bar_formatter \
      --format Zenspec::Formatters::ProgressBarFormatter

Constant Summary collapse

COLORS =

ANSI color codes

{
  green: "\e[32m",
  red: "\e[31m",
  yellow: "\e[33m",
  cyan: "\e[36m",
  white: "\e[37m",
  bright_white: "\e[97m",
  reset: "\e[0m"
}.freeze
ICONS =

Status icons

{
  passed: "",
  failed: "",
  pending: ""
}.freeze
SPINNER_FRAMES =

Spinner frames for animation

["", "", "", "", "", "", "", "", "", ""].freeze
SPINNER_INTERVAL =

Spinner update interval in seconds (80ms provides smooth 12.5 FPS animation)

0.08
DEFAULT_TERMINAL_WIDTH =

Default terminal width if unable to detect (standard wide terminal)

120
MIN_PADDING =

Minimum padding between left and right parts for readability

2
ANSI_REGEX =

Pre-compiled regex for stripping ANSI color codes (performance optimization)

/\e\[[0-9;]*m/.freeze

Instance Method Summary collapse

Constructor Details

#initialize(output) ⇒ ProgressBarFormatter

Returns a new instance of ProgressBarFormatter.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 71

def initialize(output)
  super
  @current_count = 0
  @total_count = 0
  @failed_examples = []
  @pending_examples = []

  # File tracking
  @file_states = {} # filename -> {passed: 0, failed: 0, pending: 0, current_test: nil}
  @current_file = nil
  @completed_files = [] # Track files that are done

  # Spinner state
  @spinner_frame = 0
  @spinner_thread = nil
  @mutex = Mutex.new
  @spinner_running = false
end

Instance Method Details

#dump_failures(notification) ⇒ Object

Called to print failure details



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 181

def dump_failures(notification)
  return if notification.failed_examples.empty?

  output.puts
  output.puts colorize("Failures:", :red)
  output.puts

  notification.failed_examples.each_with_index do |example, index|
    output.puts colorize("  #{index + 1}) #{example.full_description}", :red)

    exception = example.execution_result.exception
    if exception
      output.puts colorize("     #{exception.class}: #{exception.message}", :red)

      # Print backtrace (first 3 lines)
      if exception.backtrace
        exception.backtrace.first(3).each do |line|
          output.puts colorize("       #{line}", :red)
        end
      end
    end

    output.puts colorize("     # #{format_location(example)}", :red)
    output.puts
  end
end

#dump_summary(summary) ⇒ Object

Called at the end to print summary



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 148

def dump_summary(summary)
  @mutex.synchronize do
    # Finalize the last file
    finalize_file(@current_file) if @current_file

    # Stop spinner
    stop_spinner
  end

  output.puts
  output.puts
  output.puts colorize("=" * terminal_width, :bright_white)
  output.puts colorize("Test Summary", :bright_white)
  output.puts colorize("=" * terminal_width, :bright_white)
  output.puts

  # Summary statistics
  output.puts summary_line(summary)
  output.puts "Duration: #{format_duration(summary.duration)}"
  output.puts

  # Pending examples
  if @pending_examples.any?
    output.puts
    output.puts colorize("Pending Examples:", :cyan)
    @pending_examples.each do |example|
      output.puts colorize("  #{ICONS[:pending]} #{example.full_description}", :cyan)
      output.puts colorize("     # #{format_location(example)}", :cyan)
    end
  end
end

#example_failed(notification) ⇒ Object

Called when an example fails



128
129
130
131
132
133
134
135
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 128

def example_failed(notification)
  @mutex.synchronize do
    filename = extract_filename(notification.example)
    @file_states[filename][:failed] += 1
    @file_states[filename][:current_test] = nil
    @failed_examples << notification.example
  end
end

#example_passed(notification) ⇒ Object

Called when an example passes



119
120
121
122
123
124
125
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 119

def example_passed(notification)
  @mutex.synchronize do
    filename = extract_filename(notification.example)
    @file_states[filename][:passed] += 1
    @file_states[filename][:current_test] = nil
  end
end

#example_pending(notification) ⇒ Object

Called when an example is pending



138
139
140
141
142
143
144
145
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 138

def example_pending(notification)
  @mutex.synchronize do
    filename = extract_filename(notification.example)
    @file_states[filename][:pending] += 1
    @file_states[filename][:current_test] = nil
    @pending_examples << notification.example
  end
end

#example_started(notification) ⇒ Object

Called when an example starts



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 97

def example_started(notification)
  @mutex.synchronize do
    @current_count += 1
    filename = extract_filename(notification.example)

    # Check if we moved to a new file
    if @current_file && @current_file != filename
      # Finalize the previous file
      finalize_file(@current_file)
    end

    # Initialize file state if needed
    @file_states[filename] ||= { passed: 0, failed: 0, pending: 0, current_test: nil }
    @file_states[filename][:current_test] = notification.example.description
    @current_file = filename

    # Start spinner if not already running
    start_spinner unless @spinner_running
  end
end

#start(notification) ⇒ Object

Called at the start of the test suite



91
92
93
94
# File 'lib/zenspec/formatters/progress_bar_formatter.rb', line 91

def start(notification)
  @total_count = notification.count
  output.puts
end