File Coverage

blib/lib/Test/BDD/Cucumber/StepContext.pm
Criterion Covered Total %
statement 64 79 81.0
branch 21 34 61.7
condition 8 18 44.4
subroutine 14 15 93.3
pod 5 5 100.0
total 112 151 74.1


line stmt bran cond sub pod time code
1 12     12   182 use v5.14;
  12         45  
2 12     12   87 use warnings;
  12         72  
  12         584  
3              
4             package Test::BDD::Cucumber::StepContext 0.85;
5              
6 12     12   132 use Moo;
  12         30  
  12         103  
7 12     12   4777 use Types::Standard qw( Bool Str HashRef ArrayRef InstanceOf );
  12         29  
  12         103  
8 12     12   12522 use List::Util qw( first );
  12         29  
  12         6501  
9              
10             =head1 NAME
11              
12             Test::BDD::Cucumber::StepContext - Data made available to step definitions
13              
14             =head1 VERSION
15              
16             version 0.85
17              
18             =head1 DESCRIPTION
19              
20             The coderefs in Step Definitions have a single argument passed to them, a
21             C object. This is an attribute-only class,
22             populated by L.
23              
24             When steps are run normally, C is set directly before execution to return
25             the context; this allows you to do:
26              
27             sub { return C->columns }
28              
29             instead of:
30              
31             sub { my $c = shift; return $c->columns; }
32              
33             =head1 ATTRIBUTES
34              
35             =head2 columns
36              
37             If the step-specific data supplied is a table, the this attribute will contain
38             the column names in the order they appeared.
39              
40             =cut
41              
42             has 'columns' => ( is => 'ro', isa => ArrayRef );
43              
44             =head2 _data
45              
46             Step-specific data. Will either be a text string in the case of a """ string, or
47             an arrayref of hashrefs if the step had an associated table.
48              
49             See the C method below.
50              
51             =cut
52              
53             has '_data' =>
54             ( is => 'ro', isa => Str|ArrayRef, init_arg => 'data', default => '' );
55              
56             =head2 stash
57              
58             A hash of hashes, containing two keys, C, C.
59             The stash allows you to persist data across features or scenarios.
60              
61             The scenario-level stash is also available to steps by calling C, making
62             the following two lines of code equivalent:
63              
64             sub { my $context = shift; my $stash = $context->stash->{'scenario'}; $stash->{'count'} = 1 }
65             sub { S->{'count'} = 1 }
66              
67             =cut
68              
69             has 'stash' => ( is => 'ro', required => 1, isa => HashRef );
70              
71             =head2 feature
72              
73             =head2 scenario
74              
75             =head2 step
76              
77             Links to the L,
78             L, and L
79             objects respectively.
80              
81             =cut
82              
83             has 'feature' => (
84             is => 'ro',
85             required => 1,
86             isa => InstanceOf['Test::BDD::Cucumber::Model::Feature']
87             );
88             has 'scenario' => (
89             is => 'ro',
90             required => 1,
91             isa => InstanceOf['Test::BDD::Cucumber::Model::Scenario']
92             );
93             has 'step' =>
94             ( is => 'ro', required => 0, isa => InstanceOf['Test::BDD::Cucumber::Model::Step'] );
95              
96             =head2 verb
97              
98             The lower-cased verb a Step Definition was called with.
99              
100             =cut
101              
102             has 'verb' => ( is => 'ro', required => 1, isa => Str );
103              
104             =head2 text
105              
106             The text of the step, minus the verb. Placeholders will have already been
107             multiplied out at this point.
108              
109             =cut
110              
111             has 'text' => ( is => 'ro', required => 1, isa => Str, default => '' );
112              
113             =head2 harness
114              
115             The L harness being used by the executor.
116              
117             =cut
118              
119             has 'harness' =>
120             ( is => 'ro', required => 1, isa => InstanceOf['Test::BDD::Cucumber::Harness'] );
121              
122             =head2 executor
123              
124             Weak reference to the L being used - this allows
125             for step redispatch.
126              
127             =cut
128              
129             has 'executor' => (
130             is => 'ro',
131             required => 1,
132             isa => InstanceOf['Test::BDD::Cucumber::Executor'],
133             weak_ref => 1
134             );
135              
136             =head2 matches
137              
138             Any matches caught by the Step Definition's regex. These are also available as
139             C<$1>, C<$2> etc as appropriate.
140              
141             =cut
142              
143             has '_matches' => (
144             is => 'rw',
145             isa => ArrayRef,
146             init_arg => 'matches',
147             default => sub { [] }
148             );
149              
150             has 'transformers' =>
151             ( is => 'ro', isa => ArrayRef, predicate => 'has_transformers', );
152              
153             has '_transformed_matches' => (
154             is => 'ro',
155             isa => ArrayRef,
156             lazy => 1,
157             builder => '_build_transformed_matches',
158             clearer => '_clear_transformed_matches',
159             );
160              
161             has '_transformed_data' => (
162             is => 'ro',
163             isa => Str|ArrayRef,
164             lazy => 1,
165             builder => '_build_transformed_data',
166             clearer => '_clear_transformed_data',
167             );
168              
169             =head2 is_hook
170              
171             The harness processing the output can decide whether to shop information for
172             this step which is actually an internal hook, i.e. a Before or After step
173              
174             =cut
175              
176             has 'is_hook' =>
177             ( is => 'ro', isa => Bool, lazy => 1, builder => '_build_is_hook' );
178              
179             =head2 parent
180              
181             If a step redispatches to another step, the child step will have a link back to
182             its parent step here; otherwise undef. See L.
183              
184             =cut
185              
186             has 'parent' => ( is => 'ro', isa => InstanceOf['Test::BDD::Cucumber::StepContext'] );
187              
188             =head1 METHODS
189              
190             =head2 background
191              
192             Boolean for "is this step being run as part of the background section?".
193             Currently implemented by asking the linked Scenario object...
194              
195             =cut
196              
197 0     0 1 0 sub background { my $self = shift; return $self->scenario->background }
  0         0  
198              
199             =head2 data
200              
201             See the C<_data> attribute above.
202              
203             Calling this method will return either the """ string, or a possibly Transform-ed
204             set of table data.
205              
206             =cut
207              
208             sub data {
209 29     29 1 113 my $self = shift;
210              
211 29 50       128 if (@_) {
212 0         0 $self->_data(@_);
213 0         0 $self->_clear_transformed_data;
214 0         0 return;
215             }
216              
217 29         620 return $self->_transformed_data;
218             }
219              
220             =head2 matches
221              
222             See the C<_matches> attribute above.
223              
224             Call this method will return the possibly Transform-ed matches .
225              
226             =cut
227              
228             sub matches {
229 848     848 1 1648 my $self = shift;
230              
231 848 100       2180 if (@_) {
232 622         14369 $self->_matches(@_);
233 622         26550 $self->_clear_transformed_matches;
234 622         3766 return;
235             }
236              
237 226         4459 return $self->_transformed_matches;
238             }
239              
240             =head2 transform
241              
242             Used internally to transform data and placeholders, but it can also be called
243             from within your Given/When/Then code.
244              
245             =cut
246              
247             sub transform {
248 305     305 1 546 my $self = shift;
249 305         536 my $value = shift;
250              
251 305 100       682 defined $value or return $value;
252              
253             TRANSFORM:
254 293         487 for my $transformer ( @{ $self->transformers } ) {
  293         1139  
255              
256             # turn off this warning so undef can be set in the following regex
257 12     12   107 no warnings 'uninitialized';
  12         47  
  12         9735  
258              
259             # uses the same magic as other steps
260             # and puts any matches into $1, $2, etc.
261             # and calls the Transform step
262              
263             # also, if the transformer code ref returns undef, this will be coerced
264             # into an empty string, so need to mark it as something else
265             # and then turn it into proper undef
266              
267 748 100       2919 if (
268             $value =~ s/$transformer->[0]/
269 4         27 my $value = $transformer->[2]->( $self );
270 4 50       29 defined $value ? $value : '__UNDEF__'
271             /e
272             )
273             {
274             # if we matched then stop processing this match
275 4 50       33 return $value eq '__UNDEF__' ? undef : $value;
276             }
277             }
278              
279             # if we're here, the value will be returned unchanged
280 289         1107 return $value;
281             }
282              
283             =head1 Redispatching
284              
285             Sometimes you want to call one step from another step. You can do this via the
286             I, using the C method. For example:
287              
288             Given qr/I have entered (\d+)/, sub {
289             C->dispatch( 'Given', "I have pressed $1");
290             C_>dispatch( 'Given', "I have passed-in data", C->data );
291             C->dispatch( 'Given', "I have pressed enter", { some => 'data' } );
292             };
293              
294             You redispatch step will have its own, new step context with almost everything
295             copied from the parent step context. However, specifically not copied are:
296             C, C, the C object, and of course the C and the
297             C.
298              
299             If you want to pass data to your child step, you should IDEALLY do it via the
300             text of the step itself, or failing that, through the scenario-level stash.
301             Otherwise it'd make more sense just to be calling some subroutine... But you
302             B pass in a third argument - a hashref which will be used as C. The
303             data in that third argument can be one of:
304              
305             =over
306              
307             =item * a string
308              
309             This scenario corresponds with having a C<""" ... """> string argument
310             to the step. It's passed to the child step verbatim.
311              
312             =item * a hash reference (deprecated)
313              
314             This scenario corresponds with the third example above and has been
315             supported historically. There is no good reason to use this type of
316             argument passing, because there is no way for a feature to pass data
317             to the step. When you need to use this scenario, please consider
318             implementing a separate subroutine instead.
319              
320             =item * a reference to an array of hashes
321              
322             This scenario corresponsds with a data table argument to the step. The
323             names of the columns are taken from the first hash in the array (the
324             first row in the data table).
325              
326             No transformations are applied to the table passed in to prevent
327             duplicate transformations being applied.
328              
329             =back
330              
331             The value of the third argument will be used as the C<< C->data >> value
332             for the C of the child step. All values passed in, will be
333             passed to the child without applying C declarations. That way,
334             double transformation is prevented.
335              
336             If the step you dispatch to doesn't pass for any reason (can't be found, dies,
337             fails, whatever), it'll throw an exception. This will get caught by the parent
338             step, which will then fail, and show debugging output.
339              
340             B
341             the parser. Also, remember to quote them as if you're in a step file, there may
342             be a subroutine defined with the same name.>
343              
344             =head2 dispatch
345              
346             C->dispatch( 'Then', "the page has loaded successfully");
347              
348             See the paragraphs immediately above this
349              
350             =cut
351              
352             sub dispatch {
353 12     12 1 33 my ( $self, $verb, $text, $data ) = @_;
354              
355 12         297 my $step = Test::BDD::Cucumber::Model::Step->new(
356             {
357             text => $text,
358             verb => $verb,
359             line => Test::BDD::Cucumber::Model::Line->new(
360             {
361             number => $self->step->line->number,
362             raw_content => "[Redispatched step: $verb $text]",
363             document => $self->step->line->document,
364             }
365             ),
366             }
367             );
368              
369 12         1136 my $columns;
370 12 50       37 if ($data) {
371 0 0 0     0 if ( ref $data eq 'HASH' ) {
    0 0        
372 0         0 $columns = [ sort keys %$data ];
373             }
374             elsif ( ref $data eq 'ARRAY'
375 0         0 and (scalar @{ $data } > 0)
376             and ref $data->[0] eq 'HASH' ) {
377 0         0 $columns = [ sort keys %{ $data->[0] } ];
  0         0  
378             }
379             }
380              
381             my $new_context = $self->new(
382             {
383             executor => $self->executor,
384             ( $data ? ( data => $data ) : () ),
385             ( $data ? ( _transformed_data => $data ) : () ),
386             ( $columns ? ( columns => $columns ) : () ),
387             stash => {
388             feature => $self->stash->{'feature'},
389 12 50       416 scenario => $self->stash->{'scenario'},
    50          
    50          
390             step => {},
391             },
392             feature => $self->feature,
393             scenario => $self->scenario,
394             harness => $self->harness,
395             transformers => $self->transformers,
396              
397             step => $step,
398             verb => lc($verb),
399             text => $text,
400             }
401             );
402              
403 12         2314 my $result = $self->executor->find_and_dispatch( $new_context, 0, 1 );
404              
405             # If it didn't pass, short-circuit the rest
406 12 50       45 unless ( $result->result eq 'passing' ) {
407 0         0 my $error = "Redispatched step didn't pass:\n";
408 0         0 $error .= "\tStatus: " . $result->result . "\n";
409 0         0 $error .= "\tOutput: " . $result->output . "\n";
410 0         0 $error .= "Failure to redispatch a step causes the parent to fail\n";
411 0         0 die $error;
412             }
413              
414 12         184 return $result;
415             }
416              
417             # the builder for the is_hook attribute
418             sub _build_is_hook {
419 499     499   5280 my $self = shift;
420              
421 499 100 100     9557 return ( $self->verb eq 'before' or $self->verb eq 'after' ) ? 1 : 0;
422             }
423              
424             # the builder for the _transformed_matches attribute
425             sub _build_transformed_matches {
426 226     226   2296 my $self = shift;
427              
428 226         326 my @transformed_matches = @{ $self->_matches };
  226         3606  
429              
430             # this stops it recursing forever...
431             # and only Transform if there are any to process
432 226 50 33     2731 if ( $self->verb ne 'transform'
433             and $self->has_transformers )
434             {
435             @transformed_matches = map {
436 226         516 my $match = $_;
  305         557  
437 305         637 $match = $self->transform($match);
438             } @transformed_matches;
439             }
440              
441 226         4153 return \@transformed_matches;
442             }
443              
444             # the builder for the _transformed_data attribute
445             sub _build_transformed_data {
446 29     29   445 my $self = shift;
447              
448 29         115 my $transformed_data = $self->_data;
449              
450             # again stop recursing
451             # only transform table data
452             # and only Transform if there are any to process
453 29 100 66     287 if ( $self->verb ne 'transform'
      66        
454             and ref $transformed_data
455             and $self->has_transformers )
456             {
457             # build the string that a Transform is looking for
458             # table:column1,column2,column3
459 7         21 my $table_text = 'table:' . join( ',', @{ $self->columns } );
  7         79  
460              
461 7 100       44 if ( my $transformer =
462 10     10   87 first { $table_text =~ $_->[0] } @{ $self->transformers } )
  7         45  
463             {
464             # call the Transform step
465 2         11 $transformer->[2]->( $self, $transformed_data );
466             }
467             }
468              
469 29         540 return $transformed_data;
470             }
471              
472             =head1 AUTHOR
473              
474             Peter Sergeant C
475              
476             =head1 LICENSE
477              
478             Copyright 2019-2023, Erik Huelsmann
479             Copyright 2011-2019, Peter Sergeant; Licensed under the same terms as Perl
480              
481             =cut
482              
483             1;