File Coverage

blib/lib/Test/BDD/Cucumber/StepContext.pm
Criterion Covered Total %
statement 59 74 79.7
branch 21 34 61.7
condition 8 18 44.4
subroutine 12 13 92.3
pod 5 5 100.0
total 105 144 72.9


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