File Coverage

blib/lib/Moonshine/Test.pm
Criterion Covered Total %
statement 112 117 95.7
branch 49 60 81.6
condition 2 4 50.0
subroutine 43 44 97.7
pod 4 4 100.0
total 210 229 91.7


line stmt bran cond sub pod time code
1             package Moonshine::Test;
2              
3 7     7   880558 use strict;
  7         16  
  7         283  
4 7     7   69 use warnings;
  7         13  
  7         415  
5 7     7   4399 use Test::More;
  7         64676  
  7         71  
6 7     7   4110 use Scalar::Util qw/blessed/;
  7         19  
  7         349  
7 7     7   4626 use Params::Validate qw/:all/;
  7         84695  
  7         1805  
8 7     7   67 use B qw/svref_2object/;
  7         17  
  7         928  
9 7     7   48 use Exporter 'import';
  7         12  
  7         323  
10 7     7   4195 use Acme::AsciiEmoji;
  7         78118  
  7         104  
11 7     7   69277 use Switch::Again qw/switch/;
  7         146153  
  7         67  
12              
13             our @EMO = @Acme::AsciiEmoji::EXPORT_OK;
14             our @EXPORT = qw/render_me moon_test moon_test_one sunrise/;
15             our @EXPORT_OK = (qw/render_me moon_test moon_test_one sunrise/, @EMO);
16             our %EXPORT_TAGS = (
17             all => [qw/render_me moon_test moon_test_one sunrise/, @EMO],
18             element => [qw/render_me sunrise/],
19             emo => [@EMO],
20             );
21              
22              
23             =head1 NAME
24              
25             Moonshine::Test - Test!
26              
27             =head1 VERSION
28              
29             Version 0.18
30              
31             =cut
32              
33             our $VERSION = '0.18';
34              
35             =head1 SYNOPSIS
36              
37             use Moonshine::Test qw/:all/;
38              
39             moon_test_one(
40             test => 'scalar',
41             meth => \&Moonshine::Util::append_str,
42             args => [
43             'first', 'second'
44             ],
45             args_list => 1,
46             expected => 'first second',
47             );
48              
49             sunrise(1);
50              
51             =head1 EXPORT
52              
53             =head2 all
54              
55             =over
56              
57             =item moon_test
58              
59             =item moon_test_one
60              
61             =item render_me
62              
63             =item done_testing
64              
65             =back
66              
67             =head2 element
68              
69             =over
70              
71             =item render_me
72              
73             =item done_testing
74              
75             =back
76              
77             =head1 SUBROUTINES/METHODS
78              
79             =head2 moon_test_one
80              
81             moon_test_one(
82             test => 'render_me',
83             instance => Moonshine::Component->new(),
84             func => 'button',
85             args => {
86             data => '...'
87             },
88             expected => '',
89             );
90              
91             =head2 Instructions
92              
93             Valid instructions moon_test_one accepts
94              
95             =head3 test/expected
96              
97             test => 'like'
98             expected => 'a horrible death'
99             ....
100             like($test_outcome, qr/$expected/, "function: $func is like - $expected");
101              
102             moon_test_one can currently run the following tests.
103              
104             =over
105              
106             =item ok - ok - a true value
107              
108             =item ref - is_deeply - expected [] or {}
109              
110             =item scalar - is - expected '',
111              
112             =item hash - is_deeply - expected {},
113              
114             =item array - is_deeply - expected [],
115              
116             =item obj - isa_ok - expected '',
117              
118             =item like - like - '',
119              
120             =item true - is - 1,
121              
122             =item false - is - 0,
123              
124             =item undef - is - undef
125              
126             =item ref_key_scalar - is - '' (requires key)
127              
128             =item ref_key_ref - is_deeply - [] or {} (requires key)
129              
130             =item ref_key_like - like - ''
131              
132             =item ref_index_scalar - is - '' (requires index)
133              
134             =item ref_index_ref - is_deeply - [] or {} (required index)
135              
136             =item ref_index_like - like - ''
137              
138             =item ref_index_obj - isa_ok - ''
139              
140             =item list_key_scalar - is - '' (requires key)
141              
142             =item list_key_ref - is_deeply - [] or {} (requires key)
143              
144             =item list_key_like - like - ''
145              
146             =item list_index_scalar - is - '' (requires index)
147              
148             =item list_index_ref - is_deeply - [] or {} (required index)
149              
150             =item list_index_obj - isa_ok - ''
151              
152             =item list_index_like - like - ''
153              
154             =item count - is - ''
155              
156             =item count_ref - is - ''
157              
158             =item skip - ok(1)
159              
160             =back
161              
162             =head3 catch
163              
164             when you want to catch exceptions....
165              
166             catch => 1,
167              
168             defaults the instruction{test} to like.
169              
170             =head3 instance
171              
172             my $instance = Moonshine::Element->new();
173             instance => $instance,
174              
175             =head3 func
176              
177             call a function from the instance
178              
179             instance => $instance,
180             func => 'render'
181              
182             =head3 meth
183              
184             meth => \&Moonshine::Element::render,
185              
186             =head3 args
187              
188             {} or []
189              
190             =head3 args_list
191              
192             args => [qw/one, two/],
193             args_list => 1,
194              
195             =head3 index
196              
197             index - required when testing - ref_index_*
198              
199             =head3 key
200              
201             key - required when testing - ref_key_*
202              
203             =cut
204              
205             sub moon_test_one {
206 86     86 1 638693 my %instruction = validate_with(
207             params => \@_,
208             spec => {
209             instance => 0,
210             meth => 0,
211             func => 0,
212             args => { default => {} },
213             args_list => 0,
214             test => 0,
215             expected => 0,
216             catch => 0,
217             key => 0,
218             index => 0,
219             built => 0,
220             }
221             );
222              
223 86         908 my @test = ();
224 86         199 my $test_name = '';
225 86         307 my @expected = $instruction{expected};
226              
227 86 100       264 if ( $instruction{catch} ) {
228 3         5 $test_name = 'catch';
229 3 50       14 exists $instruction{test} or $instruction{test} = 'like';
230 3         4 eval { _run_the_code( \%instruction ) };
  3         11  
231 3         162 @test = $@;
232             }
233             else {
234 83         364 @test = _run_the_code( \%instruction );
235 83         6520 $test_name = shift @test;
236             }
237              
238 86 100       283 if ( not exists $instruction{test} ) {
239 1         7 ok(0);
240 1         415 diag 'No instruction{test} passed to moon_test_one';
241 1         136 return;
242             }
243              
244             switch( $instruction{test},
245             'ref' => sub {
246 6     6   1411 return is_deeply( $test[0], $expected[0],
247             "$test_name is ref - is_deeply" );
248             },
249             'ref_key_scalar' => sub {
250             return exists $instruction{key}
251             ? is(
252             $test[0]->{ $instruction{key} },
253 3 100   3   687 $expected[0],
254             "$test_name is ref - has scalar key: $instruction{key} - is - $expected[0]"
255             )
256             : ok(
257             0,
258             "No key passed to test - ref_key_scalar - testing - $test_name"
259             );
260             },
261             'ref_key_like' => sub {
262             return exists $instruction{key}
263             ? like(
264             $test[0]->{ $instruction{key} },
265 3 100   3   839 qr/$expected[0]/,
266             "$test_name is ref - has scalar key: $instruction{key} - like - $expected[0]"
267             )
268             : ok( 0,
269             "No key passed to test - ref_key_like - testing - $test_name" );
270             },
271             'ref_key_ref' => sub {
272             return exists $instruction{key}
273             ? is_deeply(
274             $test[0]->{ $instruction{key} },
275 5 100   5   1390 $expected[0],
276             "$test_name is ref - has ref key: $instruction{key} - is_deeply - ref"
277             )
278             : ok( 0,
279             "No key passed to test - ref_key_ref - testing - $test_name" );
280             },
281             'ref_index_scalar' => sub {
282             return exists $instruction{index}
283             ? is(
284 6 100   6   1867 $test[0]->[ $instruction{index} ],
285             $expected[0],
286             "$test_name is ref - has scalar index: $instruction{index} - is - $expected[0]"
287             )
288             : ok(
289             0,
290             "No index passed to test - ref_index_scalar - testing - $test_name"
291             );
292             },
293             'ref_index_ref' => sub {
294             return exists $instruction{index}
295             ? is_deeply(
296 3 100   3   1018 $test[0]->[ $instruction{index} ],
297             $expected[0],
298             "$test_name is ref - has ref index: $instruction{index} - is_deeply - ref"
299             )
300             : ok(
301             0,
302             "No index passed to test - ref_index_ref - testing - $test_name"
303             );
304             },
305             'ref_index_like' => sub {
306             return exists $instruction{index}
307             ? like(
308 3 100   3   881 $test[0]->[ $instruction{index} ],
309             qr/$expected[0]/,
310             "$test_name is ref - has scalar index: $instruction{index} - like - $expected[0]"
311             )
312             : ok(
313             0,
314             "No index passed to test - ref_index_like - testing - $test_name"
315             );
316             },
317             'ref_index_obj' => sub {
318             return exists $instruction{index}
319             ? isa_ok(
320 1 50   1   271 $test[0]->[ $instruction{index} ],
321             $expected[0],
322             "$test_name is ref - has obj index: $instruction{index} - isa_ok - $expected[0]"
323             )
324             : ok(
325             0,
326             "No index passed to test - ref_index_obj - testing - $test_name"
327             );
328             },
329             'list_index_scalar' => sub {
330             return exists $instruction{index}
331             ? is(
332 3 100   3   921 $test[ $instruction{index} ],
333             $expected[0],
334             "$test_name is list - has scalar index: $instruction{index} - is - $expected[0]"
335             )
336             : ok(
337             0,
338             "No index passed to test - list_index_scalar - testing - $test_name"
339             );
340             },
341             'list_index_ref' => sub {
342             return exists $instruction{index}
343             ? is_deeply(
344 3 100   3   926 $test[ $instruction{index} ],
345             $expected[0],
346             "$test_name is list - has ref index: $instruction{index} - is_deeply - ref"
347             )
348             : ok(
349             0,
350             "No index passed to test - list_index_ref - testing - $test_name"
351             );
352             },
353             'list_index_like' => sub {
354             return exists $instruction{index}
355             ? like(
356 3 100   3   1032 $test[ $instruction{index} ],
357             qr/$expected[0]/,
358             "$test_name is list - has scalar index: $instruction{index} - like - $expected[0]"
359             )
360             : ok(
361             0,
362             "No index passed to test - list_index_like - testing - $test_name"
363             );
364             },
365             'list_index_obj' => sub {
366             return exists $instruction{index}
367             ? isa_ok(
368 1 50   1   622 $test[ $instruction{index} ],
369             $expected[0],
370             "$test_name is list - has obj index: $instruction{index} - isa_ok - $expected[0]"
371             )
372             : ok(
373             0,
374             "No index passed to test - list_index_obj - testing - $test_name"
375             );
376             },
377             'list_key_scalar' => sub {
378             return exists $instruction{key}
379             ? is(
380             {@test}->{ $instruction{key} },
381 3 100   3   1091 $expected[0],
382             "$test_name is list - has scalar key: $instruction{key} - is - $expected[0]"
383             )
384             : ok(
385             0,
386             "No key passed to test - list_key_scalar - testing - $test_name"
387             );
388             },
389             'list_key_ref' => sub {
390             return exists $instruction{key}
391             ? is_deeply(
392             {@test}->{ $instruction{key} },
393 3 100   3   1172 $expected[0],
394             "$test_name is list - has ref key: $instruction{key} - is_deeply - ref"
395             )
396             : ok( 0,
397             "No key passed to test - list_key_ref - testing - $test_name" );
398             },
399             'list_key_like' => sub {
400             return exists $instruction{key}
401             ? like(
402             {@test}->{ $instruction{key} },
403 3 100   3   1245 qr/$expected[0]/,
404             "$test_name is list - has scalar key: $instruction{key} - like - $expected[0]"
405             )
406             : ok(
407             0,
408             "No key passed to test - list_key_like - testing - $test_name"
409             );
410             },
411             'count' => sub {
412 1     1   494 return is(
413             scalar @test,
414             $expected[0],
415             "$test_name is list - count - is - $expected[0]"
416             );
417             },
418             'count_ref' => sub {
419             return is(
420 2     2   981 scalar @{ $test[0] },
  2         18  
421             $expected[0],
422             "$test_name is ref - count - is - $expected[0]"
423             );
424             },
425             'scalar' => sub {
426 2 50   2   1224 return is( $test[0], $expected[0], sprintf "%s is scalar - is - %s",
427             $test_name, defined $expected[0] ? $expected[0] : 'undef' );
428             },
429             'hash' => sub {
430 3     3   1844 return is_deeply( {@test}, $expected[0],
431             "$test_name is hash - reference - is_deeply" );
432             },
433             'array' => sub {
434 5     5   2884 return is_deeply( \@test, $expected[0],
435             "$test_name is array - reference - is_deeply" );
436             },
437             'obj' => sub {
438 7     7   4385 return isa_ok( $test[0], $expected[0],
439             "$test_name is Object - blessed - is - $expected[0]" );
440             },
441             'like' => sub {
442 3     3   1547 return like( $test[0], qr/$expected[0]/,
443             "$test_name is like - $expected[0]" );
444             },
445             'true' => sub {
446 2     2   1100 return is( $test[0], 1, "$test_name is true - 1" );
447             },
448             'false' => sub {
449 2     2   1225 return is( $test[0], 0, "$test_name is false - 0" );
450             },
451             'undef' => sub {
452 2     2   1174 return is( $test[0], undef, "$test_name is undef" );
453             },
454             'render' => sub {
455 4     4   2638 return render_me(
456             instance => $test[0],
457             expected => $expected[0],
458             );
459             },
460             'ok' => sub {
461 2     2   1249 return ok(@test, "$test_name is ok");
462             },
463             'skip' => sub {
464 1     1   470 return ok(1, "$test_name - skip");
465             },
466             default => sub {
467 0     0   0 ok(0);
468 0         0 diag "Unknown instruction{test}: $_ passed to moon_test_one";
469 0         0 return;
470             }
471 85         5722 );
472             }
473              
474             =head2 moon_test
475            
476             moon_test(
477             name => 'Checking Many Things'
478             build => {
479             class => 'Moonshine::Element',
480             args => {
481             tag => 'p',
482             text => 'hello'
483             }
484             },
485             instructions => [
486             {
487             test => 'scalar',
488             func => 'tag',
489             expected => 'p',
490             },
491             {
492             test => 'scalar',
493             action => 'text',
494             expected => 'hello',
495             },
496             {
497             test => 'render'
498             expected => '

hello

'
499             },
500             ],
501             );
502              
503             =head3 name
504              
505             The tests name
506              
507             name => 'I rule the world',
508              
509             =head3 instance
510              
511             my $instance = My::Object->new();
512             instance => $instance,
513              
514             =head3 build
515              
516             Build an instance
517              
518             build => {
519             class => 'My::Object',
520             args => { },
521             },
522              
523             =head3 instructions
524              
525             instructions => [
526             {
527             test => 'scalar',
528             func => 'tag',
529             expected => 'hello',
530             },
531             {
532             test => 'scalar',
533             action => 'text',
534             expected => 'hello',
535             },
536             {
537             test => 'render'
538             expected => '

hello

'
539             },
540             ],
541              
542             =head3 subtest
543              
544             instructions => [
545             {
546             test => 'obj',
547             func => 'glyphicon',
548             args => { switch => 'search' },
549             subtest => [
550             {
551             test => 'scalar',
552             func => 'class',
553             expected => 'glyphicon glyphicon-search',
554             },
555             ...
556             ]
557             }
558             ]
559              
560             =cut
561              
562             sub moon_test {
563 7     7 1 736323 my %instruction = validate_with(
564             params => \@_,
565             spec => {
566             build => { type => HASHREF, optional => 1, },
567             instance => { optional => 1, },
568             instructions => { type => ARRAYREF },
569             name => { type => SCALAR },
570             }
571             );
572              
573             my $instance =
574             $instruction{build}
575             ? _build_me( $instruction{build} )
576 7 50       85 : $instruction{instance};
577              
578 7         33 my %test_info = (
579             fail => 0,
580             tested => 0,
581             );
582              
583 7         17 foreach my $test ( @{ $instruction{instructions} } ) {
  7         27  
584 40         32610 $test_info{tested}++;
585 40 100       163 if ( my $subtests = delete $test->{subtest} ) {
586             my ( $test_name, $new_instance ) = _run_the_code(
587             {
588             instance => $instance,
589 2         6 %{$test}
  2         16  
590             }
591             );
592              
593             $test_info{fail}++
594             unless moon_test_one(
595             instance => $new_instance,
596             test => $test->{test},
597             expected => $test->{expected},
598 2 50       219 );
599              
600              
601 2         3350 my $new_instructions = {
602             instance => $new_instance,
603             instructions => $subtests,
604             name => "Subtest -> $instruction{name} -> $test_name",
605             };
606            
607 2         6 moon_test(%{$new_instructions});
  2         16  
608 2         863 next;
609             }
610              
611             $test_info{fail}++
612             unless moon_test_one(
613             instance => $instance,
614 38 50       72 %{$test}
  38         192  
615             );
616             }
617              
618 7 50       4790 $test_info{ok} = $test_info{fail} ? 0 : 1;
619             return ok(
620             $test_info{ok},
621             sprintf(
622             "moon_test: %s - tested %d instructions - success: %d - failure: %d",
623             $instruction{name}, $test_info{tested},
624             ( $test_info{tested} - $test_info{fail} ), $test_info{fail},
625             )
626 7         101 );
627             }
628              
629             sub _build_me {
630 3     3   78 my %instruction = validate_with(
631             params => \@_,
632             spec => {
633             class => 1,
634             new => { default => 'new' },
635             args => { optional => 1, type => HASHREF },
636             }
637             );
638              
639 3         22 my $new = $instruction{new};
640             return $instruction{args}
641             ? $instruction{class}->$new( $instruction{args} )
642 3 50       33 : $instruction{class}->$new;
643             }
644              
645             =head2 render_me
646              
647             Test render directly on a Moonshine::Element.
648              
649             render_me(
650             instance => $element,
651             expected => '
echo
'
652             );
653              
654             Or test a function..
655              
656             render_me(
657             instance => $instance,
658             func => 'div',
659             args => { data => 'echo' },
660             expected => '
echo
',
661             );
662              
663             =cut
664              
665             sub render_me {
666 7     7 1 282728 my %instruction = validate_with(
667             params => \@_,
668             spec => {
669             instance => 0,
670             func => 0,
671             meth => 0,
672             args => { default => {} },
673             expected => { type => SCALAR },
674             }
675             );
676              
677 7         78 my ( $test_name, $instance ) = _run_the_code( \%instruction );
678              
679             return is( $instance->render,
680 7         389 $instruction{expected}, "render $test_name: $instruction{expected}" );
681             }
682              
683             sub _run_the_code {
684 98     98   205 my $instruction = shift;
685              
686 98         157 my $test_name;
687 98 100       377 if ( my $func = $instruction->{func} ) {
    100          
    100          
688 79         196 $test_name = "function: ${func}";
689            
690             return defined $instruction->{args}
691             ? defined $instruction->{args_list}
692             ? (
693             $test_name,
694 0         0 $instruction->{instance}->$func( @{ $instruction->{args} } )
695             )
696             : (
697             $test_name, $instruction->{instance}->$func( $instruction->{args} // {})
698             )
699 79 50 50     1227 : ( $test_name, $instruction->{instance}->$func );
    100          
700             }
701             elsif ( my $meth = $instruction->{meth} ) {
702 6         115 my $meth_name = svref_2object($meth)->GV->NAME;
703 6         81 $test_name = "method: ${meth_name}";
704             return
705             defined $instruction->{args_list}
706 0         0 ? ( $test_name, $meth->( @{ $instruction->{args} } ) )
707 6 50       31 : ( $test_name, $meth->( $instruction->{args} ) );
708             }
709             elsif ( exists $instruction->{instance} ) {
710 12         26 $test_name = 'instance';
711 12         49 return ( $test_name, $instruction->{instance} );
712             }
713              
714             die(
715 1         6 'instruction passed to _run_the_code must have a func, meth or instance'
716             );
717             }
718              
719             =head2 sunrise
720              
721             sunrise(); # done_testing();
722              
723             =cut
724              
725             sub sunrise {
726 6     6 1 112271 my $done_testing = done_testing(shift);
727 6         7379 diag explain $done_testing;
728 6   50     72851 diag sprintf( '
729             %s
730             ^^ @@@@@@@@@
731             ^^ ^^ @@@@@@@@@@@@@@@
732             @@@@@@@@@@@@@@@@@@ ^^
733             @@@@@@@@@@@@@@@@@@@@
734             ---- -- ----- -------- -- &&&&&&&&&&&&&&&&&&&& ------- ----------- ---
735             - -- - - -------------------- - -- -- -
736             - -- -- -- -- ------------- ---- - --- - --- - --
737             - -- - - ------ -- --- -- - -- -- -
738             - - - - - -- ------ - -- - --
739             - - - - -- - -',
740             shift // ' \o/ ' );
741 6         3914 return $done_testing;
742             }
743              
744             =head1 AUTHOR
745              
746             LNATION, C<< >>
747              
748             =head1 BUGS
749              
750             Please report any bugs or feature requests to C, or through
751             the web interface at L. I will be notified, and then you'll
752             automatically be notified of progress on your bug as I make changes.
753              
754             =head1 SUPPORT
755              
756             You can find documentation for this module with the perldoc command.
757              
758             perldoc Moonshine::Test
759              
760             You can also look for information at:
761              
762             =over 4
763              
764             =item * RT: CPAN's request tracker (report bugs here)
765              
766             L
767              
768             =item * Search CPAN
769              
770             L
771              
772             =back
773              
774             =head1 ACKNOWLEDGEMENTS
775              
776             =head1 LICENSE AND COPYRIGHT
777              
778             Copyright 2017->2025 Robert Acock.
779              
780             This program is free software; you can redistribute it and/or modify it
781             under the terms of the the Artistic License (2.0). You may obtain a
782             copy of the full license at:
783              
784             L
785              
786             Any use, modification, and distribution of the Standard or Modified
787             Versions is governed by this Artistic License. By using, modifying or
788             distributing the Package, you accept this license. Do not use, modify,
789             or distribute the Package, if you do not accept this license.
790              
791             If your Modified Version has been derived from a Modified Version made
792             by someone other than you, you are nevertheless required to ensure that
793             your Modified Version complies with the requirements of this license.
794              
795             This license does not grant you the right to use any trademark, service
796             mark, tradename, or logo of the Copyright Holder.
797              
798             This license includes the non-exclusive, worldwide, free-of-charge
799             patent license to make, have made, use, offer to sell, sell, import and
800             otherwise transfer the Package with respect to any patent claims
801             licensable by the Copyright Holder that are necessarily infringed by the
802             Package. If you institute patent litigation (including a cross-claim or
803             counterclaim) against any party alleging that the Package constitutes
804             direct or contributory patent infringement, then this Artistic License
805             to you shall terminate on the date that such litigation is filed.
806              
807             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
808             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
809             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
810             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
811             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
812             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
813             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
814             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
815              
816             =cut
817              
818             1; # End of Moonshine::Test