File Coverage

blib/lib/Test/Mojo/Role/Phantom.pm
Criterion Covered Total %
statement 25 32 78.1
branch 5 12 41.6
condition 2 5 40.0
subroutine 5 5 100.0
pod 1 1 100.0
total 38 55 69.0


line stmt bran cond sub pod time code
1             package Test::Mojo::Role::Phantom;
2              
3 4     4   5702 use Role::Tiny;
  4         10  
  4         32  
4              
5 4     4   859 use Test::More ();
  4         5  
  4         88  
6              
7 4     4   1937 use Mojo::Phantom;
  4         9  
  4         34  
8              
9             sub phantom_ok {
10 4     4 1 22899 my $t = shift;
11 4 100       22 my $opts = ref $_[-1] ? pop : {};
12 4         8 my $js = pop;
13              
14 4         34 my $base = $t->ua->server->nb_url;
15              
16 4         10171 my $url = $t->app->url_for(@_);
17 4 50       2783 unless ($url->is_abs) {
18 4         51 $url = $url->to_abs($base);
19             }
20              
21 4   33     721 my $phantom = $opts->{phantom} || do {
22             my %bind = (
23             ok => 'Test::More::ok',
24             is => 'Test::More::is',
25             diag => 'Test::More::diag',
26             fail => 'Test::More::fail',
27             %{ $opts->{bind} || {} },
28             );
29              
30             Mojo::Phantom->new(
31             base => $base,
32             bind => \%bind,
33             cookies => $t->ua->cookie_jar->all,
34             setup => $opts->{setup},
35             package => $opts->{package} || caller,
36             );
37             };
38              
39 4   50     286 my $name = $opts->{name} || 'all phantom tests successful';
40             my $block = sub {
41 4 100   4   3244 Test::More::plan(tests => $opts->{plan}) if $opts->{plan};
42             Mojo::IOLoop->delay(
43 4         1586 sub { $phantom->execute_url($url, $js, shift->begin) },
44             sub {
45 0         0 my ($delay, $err, $status) = @_;
46 0 0       0 if ($status) {
47 0         0 my $exit = $status >> 8;
48 0         0 my $sig = $status & 127;
49 0 0       0 my $msg = $exit ? "status: $exit" : "signal: $sig";
50 0         0 Test::More::diag("phantom exitted with $msg");
51             }
52 0 0       0 die $err if $err;
53             },
54 4         256 )->catch(sub{ Test::More::fail($_[1]) })->wait;
  4         2290  
55 4         32 };
56 4         14 local $Test::Builder::Level = $Test::Builder::Level + 1;
57 4         26 return $t->success(Test::More::subtest($name => $block));
58             }
59              
60             1;
61              
62             =head1 NAME
63              
64             Test::Mojo::Role::Phantom - Adds phantom_ok to Test::Mojo
65              
66             =head1 SYNOPSIS
67              
68             use Mojolicious::Lite;
69              
70             use Test::More;
71             use Test::Mojo::WithRoles qw/Phantom/;
72              
73             any '/' => 'index';
74              
75             my $t = Test::Mojo::WithRoles->new;
76              
77             $t->phantom_ok('/' => <<'JS');
78             var text = page.evaluate(function(){
79             return document.getElementById('name').innerHTML;
80             });
81             perl.is(text, 'Bender', 'name changed after loading');
82             JS
83              
84             done_testing;
85              
86             __DATA__
87              
88             @@ index.html.ep
89              
90            
91            
92            
93            
94            

Leela

95            
98            
99            
100              
101             =head1 DESCRIPTION
102              
103             L is a L role which adds a L method to L or a L instance.
104             This method tests the javascript behavior of the app via an external L process.
105             You must install that program and it must be in your C in order to use this method.
106              
107             The author recommends using L to manage the role application.
108             The low level interaction is handled by a L instance, but for the most part that is transparent to the test method.
109              
110             =head1 METHODS
111              
112             =head2 phantom_ok
113              
114             $t = $t->phantom_ok(@url_for, $js, \%opts)
115              
116             The arguments are as follows
117              
118             =head3 url specification
119              
120             L takes a url or arguments for L, a required string of javascript and and optional hash reference of additional arguments.
121              
122             =head3 javascript
123              
124             The javascript string will be executed once the phantom object has loaded the page in question.
125             At this point, it will have access to all the symbols of a typical phantom process as well as
126              
127             =over
128              
129             =item page
130              
131             The page object.
132              
133             =item status
134              
135             The page request status, should be C.
136              
137             =item perl
138              
139             A function which takes the name of a perl function and arguments for that function.
140             The function name and the arguments are serialized as JSON and then executed on the perl side.
141              
142             If the function dies (or is L), the test fails.
143              
144             =back
145              
146             Since it would be prohibitively expensive to start up a new phantom process for each test in the string, the entire string is executed as a subtest.
147             The test result will be success if the entire subtest is a success.
148              
149             If there is a javascript error, the subtest will fail.
150              
151             =head3 options
152              
153             The method also takes a hashreference of additional options.
154             They are as follows:
155              
156             =over
157              
158             =item name
159              
160             The name of the subtest
161              
162             =item plan
163              
164             The number of tests that are expected.
165             While not required, this is more useful than most plans in L since the transport of the commands is volatile.
166             By specifying a plan in this way, if the process exits (status zero) early or never starts, the test will still fail rather than silently pass assuming there were no tests.
167              
168             =item package
169              
170             The package that is searched for Perl functions if the function name is not fully qualified.
171              
172             =item bind
173              
174             A hash reference of key-value pairs which then have shortcuts built in the phantom process.
175             The pairs passed are merged into
176              
177             {
178             ok => 'Test::More::ok',
179             is => 'Test::More::is',
180             diag => 'Test::More::diag',
181             fail => 'Test::More::fail',
182             }
183              
184             In the phantom process you may then use the shortcut as
185              
186             perl.ok(@args)
187              
188             Which is handy if you are using a certain function often.
189              
190             Note that if the value is falsey, the key name is use as the target.
191              
192             =item setup
193              
194             A pass-through option specifying javascript to be run after the page object is created but before the url is opened.
195              
196             =item phantom
197              
198             If you need even more control, you may pass in an instance of L and it will be used.
199              
200             =back
201              
202             =head1 DESIGN GOALS
203              
204             Not enough people test their client-side javascript.
205             The primary goal is make testing js in you L app that you actually DO IT.
206             To accomplish this, I make the following goals:
207              
208             =over
209              
210             =item *
211              
212             Have the test script not depend on a running mojolicious server (i.e. start one, like L scripts can), whether that be from a js or perl file doesn't matter
213              
214             =item *
215              
216             Emit tap in a normal way in a manner that prove -l can collect tests
217              
218             =item *
219              
220             Not have to reimplement a large chunk of the test methods in either L or L.
221             Note: if some javascript library has functionality like Test::* (that emits tap and can be collected subject to the previous goals) then that would be sufficient.
222              
223             =back
224              
225             This module is the result of those goals and my limited design ability.
226             I encourage contribution, whether to this implementation or some other implementation which meets these goals!
227              
228             =head1 NOTES
229              
230             The C test itself mimics a C.
231             While this outer test behaves correctly, individual tests do not report the correct line and file, instead emitting from inside the IOLoop.
232             It is hoped that future versions of L will make correct reporting possible, but it is not yet.
233              
234             =head1 SOURCE REPOSITORY
235              
236             L
237              
238             =head1 AUTHOR
239              
240             Joel Berger, Ejoel.a.berger@gmail.comE
241              
242             =head1 COPYRIGHT AND LICENSE
243              
244             Copyright (C) 2015 by Joel Berger
245              
246             This library is free software; you can redistribute it and/or modify
247             it under the same terms as Perl itself.