File Coverage

blib/lib/Statocles/Test.pm
Criterion Covered Total %
statement 80 113 70.8
branch 16 48 33.3
condition 2 6 33.3
subroutine 9 10 90.0
pod 3 5 60.0
total 110 182 60.4


line stmt bran cond sub pod time code
1             package Statocles::Test;
2             our $VERSION = '0.086';
3             # ABSTRACT: Common test routines for Statocles
4              
5 68     68   5532543 use Statocles::Base;
  68         153  
  68         502  
6 68     68   20135 use Statocles::Util qw( dircopy derp );
  68         222  
  68         3983  
7              
8 68     68   455 use base qw( Exporter );
  68         135  
  68         102874  
9             our @EXPORT_OK = qw(
10             test_constructor test_pages build_test_site build_test_site_apps
11             build_temp_site
12             );
13              
14             #pod =sub build_test_site
15             #pod
16             #pod my $site = build_test_site( %site_args )
17             #pod
18             #pod Build a site for testing. The build and deploy will be set correctly to temporary
19             #pod directories. C<%site_args> will be given to the L<Statocles::Site|Statocles::Site>
20             #pod constructor.
21             #pod
22             #pod You must provide a C<theme> (probably using the one in C<t/share/theme>).
23             #pod
24             #pod =cut
25              
26             sub build_test_site {
27 69     69 1 158628 my ( %site_args ) = @_;
28 69         15086 require Statocles::Site;
29 69         586 require Statocles::Store;
30 69         14161 require Statocles::Deploy::File;
31              
32             my $store = $site_args{build_store}
33             ? Statocles::Store->new( delete $site_args{build_store} )
34 69 100       1119 : Path::Tiny->tempdir
35             ;
36              
37             my $deploy = $site_args{deploy}
38             ? Statocles::Deploy::File->new( delete $site_args{deploy} )
39 69 100       39462 : Path::Tiny->tempdir
40             ;
41              
42             # Give a testable logger by default, but only if we haven't asked
43             # for some verbose logging from the environment
44             my $log = $site_args{log}
45 69   66     60905 || Mojo::Log->new(
46             level => 'warn',
47             max_history_size => 500,
48             );
49              
50 69         3937 return Statocles::Site->new(
51             title => 'Example Site',
52             build_store => $store,
53             deploy => $deploy,
54             log => $log,
55             %site_args,
56             );
57             }
58              
59             #pod =sub build_test_site_apps
60             #pod
61             #pod my ( $site, $build_dir, $deploy_dir ) = build_test_site_apps( $share_dir, %site_args );
62             #pod
63             #pod Build a site for testing, with some apps. Returns the site, the build dir, and the
64             #pod deploy dir.
65             #pod
66             #pod =cut
67              
68             sub build_test_site_apps {
69 18     18 1 86540 my ( $share_dir, %site_args ) = @_;
70              
71 18         128 my $build_dir = Path::Tiny->tempdir;
72 18         27013 my $deploy_dir = Path::Tiny->tempdir;
73              
74 18         6018 $site_args{build_store}{path} = $build_dir;
75 18         62 $site_args{deploy}{path} = $deploy_dir;
76              
77 18 100       76 if ( !$site_args{apps} ) {
78 10         3053 require Statocles::App::Blog;
79 10         89 my $blog = Statocles::App::Blog->new(
80             store => $share_dir->child( qw( app blog ) ),
81             url_root => '/blog',
82             page_size => 2,
83             );
84              
85 10         2853 require Statocles::App::Basic;
86 10         85 my $basic = Statocles::App::Basic->new(
87             store => $share_dir->child( qw( app basic ) ),
88             url_root => '/',
89             );
90              
91             $site_args{apps} = {
92 10         3046 blog => $blog,
93             basic => $basic,
94             };
95             }
96              
97             return (
98             build_test_site(
99             theme => $share_dir->child( 'theme' ),
100             build_store => delete $site_args{build_store},
101             deploy => delete $site_args{deploy},
102 18         90 %site_args,
103             ),
104             $build_dir,
105             $deploy_dir,
106             );
107             }
108              
109              
110             sub test_constructor {
111 1     1 0 3888 my ( $class, %args ) = @_;
112 1         7 derp 'Statocles::Test::test_constructor is deprecated and will be removed in v1.000';
113 1 50       5 my %required = $args{required} ? ( %{ $args{required} } ) : ();
  1         4  
114 1 50       7 my %defaults = $args{default} ? ( %{ $args{default} } ) : ();
  0         0  
115 1         10 require Test::Builder;
116 1         3 local $Test::Builder::Level = $Test::Builder::Level + 1;
117              
118 1         5 my $tb = Test::Builder->new();
119              
120             $tb->subtest( $class . ' constructor' => sub {
121 1     1   863 my $got = $class->new( %required );
122 1         37 my $want = $class;
123 1         2 my $typeof = do {
124             !defined $got ? 'undefined'
125             : !ref $got ? 'scalar'
126             : !Scalar::Util::blessed($got) ? ref $got
127 1 50       9 : eval { $got->isa($want) } ? $want
  1 50       9  
    50          
    50          
128             : Scalar::Util::blessed($got);
129             };
130 1         6 $tb->is_eq($typeof, $class, 'constructor works with all required args');
131              
132 1 50       565 if ( $args{required} ) {
133             $tb->subtest( 'required attributes' => sub {
134 1         822 for my $key ( keys %required ) {
135 1         6 require Test::Exception;
136             &Test::Exception::dies_ok(sub {
137             $class->new(
138 1         41 map {; $_ => $required{ $_ } } grep { $_ ne $key } keys %required,
  0         0  
  1         25  
139             );
140 1         11 }, $key . ' is required');
141             }
142 1         9 });
143             }
144              
145 1 50       2372 if ( $args{default} ) {
146             $tb->subtest( 'attribute defaults' => sub {
147 0         0 my $obj = $class->new( %required );
148 0         0 for my $key ( keys %defaults ) {
149 0 0       0 if ( ref $defaults{ $key } eq 'CODE' ) {
150 0         0 local $_ = $obj->$key;
151 0         0 $tb->subtest( "$key default value" => $defaults{ $key } );
152             }
153             else {
154 0         0 require Test::Deep;
155 0         0 Test::Deep::cmp_deeply( $obj->$key, $defaults{ $key }, "$key default value" );
156             }
157             }
158 0         0 });
159             }
160              
161 1         23 });
162             }
163              
164             sub test_pages {
165 1     1 0 52 my ( $site, $app ) = ( shift, shift );
166 1         7 derp 'Statocles::Test::test_pages is deprecated and will be removed in v1.000';
167 1         6 require Test::Builder;
168              
169 1         3 my %opt;
170 1 50       6 if ( ref $_[0] eq 'HASH' ) {
171 1         3 %opt = %{ +shift };
  1         3  
172             }
173              
174 1         3 my %page_tests = @_;
175              
176 1         4 local $Test::Builder::Level = $Test::Builder::Level + 1;
177              
178 1         10 my $tb = Test::Builder->new();
179              
180 1         9 my @warnings;
181 1     0   7 local $SIG{__WARN__} = sub { push @warnings, $_[0] };
  0         0  
182              
183 1         25 my @pages = $app->pages;
184              
185 1         11 $tb->is_eq( scalar @pages, scalar keys %page_tests, 'correct number of pages' );
186              
187 1         849 for my $page ( @pages ) {
188 0         0 $tb->ok( $page->DOES( 'Statocles::Page' ), 'must be a Statocles::Page' );
189              
190 0         0 my $date = $page->date;
191 0         0 my $want = 'DateTime::Moonpig';
192 0         0 my $typeof = do {
193             !defined $date ? 'undefined'
194             : !ref $date ? 'scalar'
195             : !Scalar::Util::blessed($date) ? ref $date
196 0 0       0 : eval { $date->isa($want) } ? $want
  0 0       0  
    0          
    0          
197             : Scalar::Util::blessed($date);
198             };
199 0         0 $tb->is_eq( $typeof, $want, 'must set a date' );
200              
201 0 0       0 if ( !$page_tests{ $page->path } ) {
202 0         0 $tb->ok( 0, "No tests found for page: " . $page->path );
203 0         0 next;
204             }
205              
206 0         0 my $output = $page->render;
207              
208             # Handle filehandles from render
209 0 0 0     0 if ( ref $output eq 'GLOB' ) {
    0          
210 0         0 $output = do { local $/; <$output> };
  0         0  
  0         0  
211             }
212             # Handle Path::Tiny from render
213             elsif ( Scalar::Util::blessed( $output ) && $output->isa( 'Path::Tiny' ) ) {
214 0         0 $output = $output->slurp_raw;
215             }
216              
217 0 0       0 if ( $page->path =~ /[.](?:html|rss|atom)$/ ) {
    0          
218 0         0 my $dom = Mojo::DOM->new( $output );
219 0 0       0 $tb->ok( 0, "Could not parse dom" ) unless $dom;
220 0         0 $tb->subtest( 'html content: ' . $page->path, $page_tests{ $page->path }, $output, $dom );
221             }
222             elsif ( $page_tests{ $page->path } ) {
223 0         0 $tb->subtest( 'text content: ' . $page->path, $page_tests{ $page->path }, $output );
224             }
225             else {
226 0         0 $tb->ok( 0, "Unknown page: " . $page->path );
227             }
228              
229             }
230              
231 1 50       4 $tb->ok( !@warnings, "no warnings!" ) or $tb->diag( join "\n", @warnings );
232             }
233              
234              
235             #pod =sub build_temp_site
236             #pod
237             #pod my ( $tmpdir, $config_fn, $config ) = build_temp_site( $share_dir );
238             #pod
239             #pod Build a config file so we can test config loading and still use
240             #pod temporary directories
241             #pod
242             #pod =cut
243              
244             sub build_temp_site {
245 9     9 1 53702 my ( $share_dir ) = @_;
246              
247 9         60 my $tmp = Path::Tiny->tempdir;
248 9         5400 dircopy $share_dir->child( qw( app blog ) ), $tmp->child( 'blog' );
249 9         2908 dircopy $share_dir->child( 'theme' ), $tmp->child( 'theme' );
250 9         2921 $tmp->child( 'build_site' )->mkpath;
251 9         1501 $tmp->child( 'deploy_site' )->mkpath;
252 9         1094 $tmp->child( 'build_foo' )->mkpath;
253 9         1081 $tmp->child( 'deploy_foo' )->mkpath;
254              
255 9         1055 my $config = {
256             theme => {
257             class => 'Statocles::Theme',
258             args => {
259             store => $tmp->child( 'theme' ),
260             },
261             },
262              
263             build => {
264             class => 'Statocles::Store',
265             args => {
266             path => $tmp->child( 'build_site' ),
267             },
268             },
269              
270             deploy => {
271             class => 'Statocles::Deploy::File',
272             args => {
273             path => $tmp->child( 'deploy_site' ),
274             },
275             },
276              
277             blog => {
278             'class' => 'Statocles::App::Blog',
279             'args' => {
280             store => {
281             '$class' => 'Statocles::Store',
282             '$args' => {
283             path => $tmp->child( 'blog' ),
284             },
285             },
286             url_root => '/blog',
287             },
288             },
289              
290             plain => {
291             'class' => 'Statocles::App::Basic',
292             'args' => {
293             store => {
294             '$class' => 'Statocles::Store',
295             '$args' => {
296             path => "$tmp",
297             },
298             },
299             url_root => '/',
300             },
301             },
302              
303             site => {
304             class => 'Statocles::Site',
305             args => {
306             title => 'Site Title',
307             index => '/blog',
308             build_store => { '$ref' => 'build' },
309             deploy => { '$ref' => 'deploy' },
310             theme => { '$ref' => 'theme' },
311             apps => {
312             blog => { '$ref' => 'blog' },
313             plain => { '$ref' => 'plain' },
314             },
315             },
316             },
317              
318             build_foo => {
319             class => 'Statocles::Store',
320             args => {
321             path => $tmp->child( 'build_foo' ),
322             },
323             },
324              
325             deploy_foo => {
326             class => 'Statocles::Deploy::File',
327             args => {
328             path => $tmp->child( 'deploy_foo' ),
329             },
330             },
331              
332             site_foo => {
333             class => 'Statocles::Site',
334             args => {
335             title => 'Site Foo',
336             index => '/blog',
337             build_store => { '$ref' => 'build_foo' },
338             deploy => { '$ref' => 'deploy_foo' },
339             theme => '::default',
340             apps => {
341             blog => { '$ref' => 'blog' },
342             plain => { '$ref' => 'plain' },
343             },
344             },
345             },
346             };
347              
348 9         1787 my $config_fn = $tmp->child( 'site.yml' );
349 9         274 YAML::DumpFile( $config_fn, $config );
350 9         213844 return ( $tmp, $config_fn, $config );
351             }
352              
353             1;
354              
355             __END__
356              
357             =pod
358              
359             =encoding UTF-8
360              
361             =head1 NAME
362              
363             Statocles::Test - Common test routines for Statocles
364              
365             =head1 VERSION
366              
367             version 0.086
368              
369             =head1 DESCRIPTION
370              
371             This module provides some common test routines for Statocles tests.
372              
373             =head1 SUBROUTINES
374              
375             =head2 build_test_site
376              
377             my $site = build_test_site( %site_args )
378              
379             Build a site for testing. The build and deploy will be set correctly to temporary
380             directories. C<%site_args> will be given to the L<Statocles::Site|Statocles::Site>
381             constructor.
382              
383             You must provide a C<theme> (probably using the one in C<t/share/theme>).
384              
385             =head2 build_test_site_apps
386              
387             my ( $site, $build_dir, $deploy_dir ) = build_test_site_apps( $share_dir, %site_args );
388              
389             Build a site for testing, with some apps. Returns the site, the build dir, and the
390             deploy dir.
391              
392             =head2 build_temp_site
393              
394             my ( $tmpdir, $config_fn, $config ) = build_temp_site( $share_dir );
395              
396             Build a config file so we can test config loading and still use
397             temporary directories
398              
399             =head1 AUTHOR
400              
401             Doug Bell <preaction@cpan.org>
402              
403             =head1 COPYRIGHT AND LICENSE
404              
405             This software is copyright (c) 2016 by Doug Bell.
406              
407             This is free software; you can redistribute it and/or modify it under
408             the same terms as the Perl 5 programming language system itself.
409              
410             =cut