File Coverage

lib/Test/BDD/Cucumber/Harness/TermColor.pm
Criterion Covered Total %
statement 80 112 71.4
branch 18 36 50.0
condition 10 22 45.4
subroutine 17 18 94.4
pod 6 6 100.0
total 131 194 67.5


line stmt bran cond sub pod time code
1 2     2   1109 use v5.14;
  2         7  
2 2     2   12 use warnings;
  2         6  
  2         93  
3              
4             package Test::BDD::Cucumber::Harness::TermColor 0.85;
5              
6             =head1 NAME
7              
8             Test::BDD::Cucumber::Harness::TermColor - Prints colorized text to the screen
9              
10             =head1 VERSION
11              
12             version 0.85
13              
14             =head1 DESCRIPTION
15              
16             A L subclass that prints test output, colorized,
17             to the terminal.
18              
19             =head1 CONFIGURABLE ENV
20              
21             =head2 ANSI_COLORS_DISABLED
22              
23             You can use L's C to turn off colors
24             in the output.
25              
26             =cut
27              
28 2     2   13 use Moo;
  2         6  
  2         14  
29 2     2   767 use Types::Standard qw( Str HashRef FileHandle );
  2         6  
  2         17  
30              
31 2     2   2610 use Getopt::Long;
  2         10896  
  2         12  
32              
33             # Try and make the colors just work on Windows...
34             BEGIN {
35 2 0 33 2   594 if (
      33        
36             # We're apparently on Windows
37             $^O =~ /MSWin32/i &&
38              
39             # We haven't disabled coloured output for Term::ANSIColor
40             ( !$ENV{'ANSI_COLORS_DISABLED'} ) &&
41              
42             # Here's a flag you can use if you really really need to turn this fall-
43             # back behaviour off
44             ( !$ENV{'DISABLE_WIN32_FALLBACK'} )
45             )
46             {
47             # Try and load
48 0         0 eval { require Win32::Console::ANSI };
  0         0  
49 0 0       0 if ($@) {
50 0         0 print "# Install Win32::Console::ANSI to display colors properly\n";
51             }
52             }
53             }
54              
55 2     2   2982 use Term::ANSIColor;
  2         16932  
  2         145  
56 2     2   17 use Test::BDD::Cucumber::Model::Result;
  2         5  
  2         2906  
57              
58             extends 'Test::BDD::Cucumber::Harness';
59              
60             =head1 CONFIGURABLE ATTRIBUTES
61              
62             =head2 fh
63              
64             A filehandle to write output to; defaults to C
65              
66             =cut
67              
68             has 'fh' => ( is => 'rw', isa => FileHandle, default => sub { \*STDOUT } );
69              
70             =head2 theme
71              
72             Name of the theme to use for colours. Defaults to `dark`. Themes are defined
73             in the private attribute C<_themes>, and currently include `light` and `dark`
74              
75             =cut
76              
77             has theme => (
78             'is' => 'ro',
79             isa => Str,
80             lazy => 1,
81             default => sub {
82             my $theme = 'dark';
83             Getopt::Long::Configure('pass_through');
84             GetOptions( "c|theme=s" => \$theme );
85             return ($theme);
86             }
87             );
88              
89             has _themes => (
90             is => 'ro',
91             isa => HashRef[HashRef],
92             lazy => 1,
93             default => sub {
94             {
95             dark => {
96             'feature' => 'bright_white',
97             'scenario' => 'bright_white',
98             'scenario_name' => 'bright_blue',
99             'pending' => 'yellow',
100             'passing' => 'green',
101             'failed' => 'red',
102             'step_data' => 'bright_cyan',
103             },
104             light => {
105             'feature' => 'reset',
106             'scenario' => 'black',
107             'scenario_name' => 'blue',
108             'pending' => 'yellow',
109             'passing' => 'green',
110             'failed' => 'red',
111             'step_data' => 'magenta',
112             },
113             };
114             }
115             );
116              
117             sub _colors {
118 7     7   22 my $self = shift;
119 7   50     130 return $self->_themes->{ $self->theme }
120             || die( 'Unknown color theme [' . $self->theme . ']' );
121             }
122              
123             my $margin = 2;
124             my $current_feature;
125              
126             sub feature {
127 1     1 1 4 my ( $self, $feature ) = @_;
128 1         21 my $fh = $self->fh;
129              
130 1         10 $current_feature = $feature;
131             $self->_display(
132             {
133             indent => 0,
134             color => $self->_colors->{'feature'},
135             text => $feature->keyword_original . ': ' . ( $feature->name || '' ),
136             follow_up =>
137 1 50 50     6 [ map { $_->content } @{ $feature->satisfaction || [] } ],
  0         0  
  1         128  
138             trailing => 1
139             }
140             );
141             }
142              
143             sub feature_done {
144 1     1 1 3 my $self = shift;
145 1         20 my $fh = $self->fh;
146 1         9 print $fh "\n";
147             }
148              
149             sub scenario {
150 1     1 1 5 my ( $self, $scenario, $dataset, $longest ) = @_;
151             my $text =
152             $scenario->keyword_original . ': '
153 1   50     19 . color( $self->_colors->{'scenario_name'} )
154             . ( $scenario->name || '' );
155              
156             $self->_display(
157             {
158             indent => 2,
159             color => $self->_colors->{'scenario'},
160             text => $text,
161             follow_up =>
162 1 50 50     89 [ map { $_->content } @{ $scenario->description || [] } ],
  0         0  
  1         46  
163             trailing => 0,
164             longest_line => ( $longest || 0 )
165             }
166             );
167             }
168              
169             sub scenario_done {
170 1     1 1 4 my $self = shift;
171 1         19 my $fh = $self->fh;
172 1         15 print $fh "\n";
173             }
174              
175       2 1   sub step { }
176              
177             sub step_done {
178 2     2 1 8 my ( $self, $context, $result, $highlights ) = @_;
179              
180 2         4 my $color;
181 2         3 my $follow_up = [];
182 2         6 my $status = $result->result;
183 2         4 my $failed = 0;
184              
185 2 100 66     21 if ( $status eq 'undefined' || $status eq 'pending' ) {
    50          
186 1         3 $color = $self->_colors->{'pending'};
187             } elsif ( $status eq 'passing' ) {
188 1         6 $color = $self->_colors->{'passing'};
189             } else {
190 0         0 $failed = 1;
191 0         0 $color = $self->_colors->{'failed'};
192 0         0 $follow_up = [ split( /\n/, $result->{'output'} ) ];
193              
194 0 0       0 if ( !$context->is_hook ) {
195 0         0 unshift @{$follow_up},
  0         0  
196             'step defined at '
197             . $context->step->line->document->filename
198             . ' line '
199             . $context->step->line->number . '.';
200             }
201             }
202              
203 2         77 my $text;
204              
205 2 50       36 if ( $context->is_hook ) {
    100          
206 0 0       0 $failed or return;
207 0         0 $text = 'In ' . ucfirst( $context->verb ) . ' Hook';
208 0         0 undef $highlights;
209             } elsif ($highlights) {
210 1         74 $text = $context->step->verb_original . ' ' . $context->text;
211 1         34 $highlights =
212             [ [ 0, $context->step->verb_original . ' ' ], @$highlights ];
213             } else {
214 1         41 $text = $context->step->verb_original . ' ' . $context->text;
215 1         16 $highlights = [ [ 0, $text ] ];
216             }
217              
218             $self->_display(
219             {
220             indent => 4,
221             color => $color,
222             text => $text,
223             highlights => $highlights,
224             highlight => $self->_colors->{'step_data'},
225             trailing => 0,
226             follow_up => $follow_up,
227 2         17 longest_line => $context->stash->{'scenario'}->{'longest_step_line'}
228             }
229             );
230              
231 2         12 $self->_note_step_data( $context->step );
232             }
233              
234             sub _note_step_data {
235 2     2   5 my ( $self, $step ) = @_;
236 2 50       6 return unless $step;
237 2         4 my @step_data = @{ $step->data_as_strings };
  2         45  
238 2 50       23 return unless @step_data;
239              
240             my $note = sub {
241 0     0   0 my ( $text, $extra_indent ) = @_;
242 0   0     0 $extra_indent ||= 0;
243              
244             $self->_display(
245             {
246             indent => 6 + $extra_indent,
247 0         0 color => $self->_colors->{'step_data'},
248             text => $text
249             }
250             );
251 0         0 };
252              
253 0 0       0 if ( ref( $step->data ) eq 'ARRAY' ) {
254 0         0 for (@step_data) {
255 0         0 $note->($_);
256             }
257             } else {
258 0         0 $note->('"""');
259 0         0 for (@step_data) {
260 0         0 $note->( $_, 2 );
261             }
262 0         0 $note->('"""');
263             }
264             }
265              
266             sub _display {
267 4     4   156 my ( $class, $options ) = @_;
268 4 50       75 my $fh = ref $class ? $class->fh : \*STDOUT;
269 4         33 $options->{'indent'} += $margin;
270              
271             # Reset it all...
272 4         25 print $fh color 'reset';
273              
274             # Print the main line
275 4         205 print $fh ' ' x $options->{'indent'};
276              
277             # Highlight as appropriate
278 4         49 my $color = color $options->{'color'};
279 4 100 66     94 if ( $options->{'highlight'} && $options->{'highlights'} ) {
280 2         5 my $reset = color 'reset';
281 2         50 my $base = color $options->{'color'};
282 2         42 my $hl = color $options->{'highlight'};
283              
284 2         37 for ( @{ $options->{'highlights'} } ) {
  2         9  
285 10         92 my ( $flag, $text ) = @$_;
286 10 100       40 print $fh $reset . ( $flag ? $hl : $base ) . $text . $reset;
287             }
288              
289             # Normal output
290             } else {
291 2         7 print $fh color $options->{'color'};
292 2         58 print $fh $options->{'text'};
293             }
294              
295             # Reset and newline
296 4         51 print $fh color 'reset';
297 4         113 print $fh "\n";
298              
299             # Print follow-up lines...
300 4 50       40 for my $line ( @{ $options->{'follow_up'} || [] } ) {
  4         19  
301 0         0 print $fh color 'reset';
302 0         0 print $fh ' ' x ( $options->{'indent'} + 4 );
303 0         0 print $fh color $options->{'color'};
304 0         0 print $fh $line;
305 0         0 print $fh color 'reset';
306 0         0 print $fh "\n";
307             }
308              
309 4 100       14 print $fh "\n" if $options->{'trailing'};
310             }
311              
312             =head1 AUTHOR
313              
314             Peter Sergeant C
315              
316             =head1 LICENSE
317              
318             Copyright 2019-2023, Erik Huelsmann
319             Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl
320              
321             =cut
322              
323             1;