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

Leela

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