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 7     7   8095 use Role::Tiny;
  7         8  
  7         45  
4              
5 7     7   997 use Test::More ();
  7         24  
  7         127  
6              
7 7     7   3122 use Mojo::Phantom;
  7         19  
  7         45  
8              
9             sub phantom_ok {
10 11     11 1 121455 my $t = shift;
11 11 100       46 my $opts = ref $_[-1] ? pop : {};
12 11         19 my $js = pop;
13              
14 11         98 my $base = $t->ua->server->nb_url;
15              
16 11         20267 my $url = $t->app->url_for(@_);
17 11 50       7052 unless ($url->is_abs) {
18 11         130 $url = $url->to_abs($base);
19             }
20              
21 11   33     1580 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             arguments => $opts->{phantom_args} // [],
40             );
41             };
42              
43 11   50     811 my $name = $opts->{name} || 'all phantom tests successful';
44             my $block = sub {
45 11 100   11   9062 Test::More::plan(tests => $opts->{plan}) if $opts->{plan};
46             Mojo::IOLoop->delay(
47 11         3926 sub { $phantom->execute_url($url, $js, shift->begin) },
48             sub {
49 0         0 my ($delay, $err, $status) = @_;
50 0 0       0 if ($status) {
51 0         0 my $exit = $status >> 8;
52 0         0 my $sig = $status & 127;
53 0 0       0 my $msg = $exit ? "status: $exit" : "signal: $sig";
54 0         0 Test::More::diag("phantom exitted with $msg");
55             }
56 0 0       0 die $err if $err;
57             },
58 11         4020 )->catch(sub{ Test::More::fail($_[1]) })->wait;
  11         4591  
59 11         60 };
60 11         26 local $Test::Builder::Level = $Test::Builder::Level + 1;
61 11         51 return $t->success(Test::More::subtest($name => $block));
62             }
63              
64             1;
65              
66             =head1 NAME
67              
68             Test::Mojo::Role::Phantom - Adds phantom_ok to Test::Mojo
69              
70             =head1 SYNOPSIS
71              
72             use Mojolicious::Lite;
73              
74             use Test::More;
75             use Test::Mojo::WithRoles qw/Phantom/;
76              
77             any '/' => 'index';
78              
79             my $t = Test::Mojo::WithRoles->new;
80              
81             $t->phantom_ok('/' => <<'JS');
82             var text = page.evaluate(function(){
83             return document.getElementById('name').innerHTML;
84             });
85             perl.is(text, 'Bender', 'name changed after loading');
86             JS
87              
88             done_testing;
89              
90             __DATA__
91              
92             @@ index.html.ep
93              
94            
95            
96            
97            
98            

Leela

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