The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use strict;
use warnings;

use Test::Most 'no_plan','die'; #tests => 32;
use Class::Sniff;

{

    package Abstract;

    sub new { bless {} => shift }
    sub foo { }
    sub bar { }
    sub baz { }

    package Child1;
    our @ISA = 'Abstract';
    sub foo { }

    package Child2;
    our @ISA = 'Abstract';
    sub foo { }
    sub bar { }

    package Grandchild;
    our @ISA = qw<Child1 Child2>;
    sub foo  { }   # diamond inheritance
    sub bar  { }   # Not a problem because it's inherited through 1 path
    sub quux { }   # no inheritance
}

# Constructor with graph and ascii representations.

can_ok 'Class::Sniff', 'new';
isa_ok my $sniff = Class::Sniff->new({ class => 'Grandchild'}), 'Class::Sniff',
  '... and the object it returns';

can_ok $sniff, 'graph';
isa_ok $sniff->graph, 'Graph::Easy', '... and the object it returns';

can_ok $sniff, 'to_string';
like $sniff->to_string, qr/\| \s+ Grandchild \s+ \|/x,
    '... and it should look sane';

# Fetch general data about object hierarchy

can_ok $sniff, 'classes';
is scalar $sniff->classes, 4,
  '... and in scalar context, should return the number of classes';

eq_or_diff [ $sniff->classes ],
  [ qw/Grandchild Child1 Abstract Child2/ ],
  '... and it should return the classes in default inheritance order';

can_ok $sniff, 'parents';
eq_or_diff [$sniff->parents], [qw/Child1 Child2/],
    '... and it should return the ordered parent classes for the target class';
eq_or_diff [$sniff->parents('Child1')], [qw/Abstract/],
    '... or the parents for the named class';

throws_ok { $sniff->parents('no_such_class') }
    qr/No such class/,
    '... and it should croak if passed an unknown class';

can_ok $sniff, 'children';

eq_or_diff [$sniff->children], [],
    '... and it should return an empty array for the target class';
eq_or_diff [$sniff->children('Child1')], [qw/Grandchild/],
    '... or the children for the named class';
eq_or_diff [$sniff->children('Abstract')], [qw/Child1 Child2/],
    '... even if it has more than one child';

throws_ok { $sniff->children('no_such_class') }
    qr/No such class/,
    '... and it should croak if passed an unknown class';

can_ok $sniff, 'methods';

eq_or_diff [sort $sniff->methods], [qw/bar foo quux/],
    '... and it should return the methods for the target class';
eq_or_diff [$sniff->methods('Child1')], [qw/foo/],
    '... or the methods for the named class';
eq_or_diff [sort $sniff->methods('Abstract')], [qw/bar baz foo new/],
    '... or the methods for the named class';

throws_ok { $sniff->methods('no_such_class') }
    qr/No such class/,
    '... and it should croak if passed an unknown class';

# ignore allows us to ignore classes matching a pattern
# This is useful if you inherit from a framework such as DBIx::Class and you
# don't want that showing up.

can_ok $sniff, 'ignore';
$sniff = Class::Sniff->new( { class => 'Grandchild', ignore => qr/Abstract/ } );

throws_ok { $sniff->methods('Abstract') }
    qr/No such class/,
    '... and ignored classes are ignored';

$sniff = Class::Sniff->new( { class => 'Grandchild', ignore => qr/Child/ } );

throws_ok { $sniff->methods('Child1') }
    qr/No such class/,
    '... and ignored classes are ignored';
throws_ok { $sniff->methods('Abstract') }
    qr/No such class/,
    '... as are all parents of those classes';

# Let them include UNIVERSAL

ok $sniff = Class::Sniff->new({ class => 'Grandchild', universal => 1 }),
    'Asking for the UNIVERSAL class should succeed';

eq_or_diff [ $sniff->classes ],
  [ qw/Grandchild Child1 Abstract UNIVERSAL Child2/ ],
  '... and it should be returned when we ask for classes';

{
    package Grandchild2;
    our @ISA = 'Child1';

    sub new { return bless {} => shift }
}

ok my $sniff2 = Class::Sniff->new({class => Grandchild2->new}),
    'Class::Sniff should access a class instance in the contructor';

can_ok $sniff2, 'combine_graphs';
isa_ok my $graph = $sniff2->combine_graphs($sniff),
    'Graph::Easy', '... and the object it returns';

can_ok 'Class::Sniff', 'new_from_namespace';
ok my @sniffs = Class::Sniff->new_from_namespace({namespace => 'Grand'}),
    '... and calling it should succeed';
is scalar(@sniffs), 2,
  '... returning the correct number of Class::Sniff objects';
$graph = $sniffs[0]->combine_graphs(@sniffs[1..$#sniffs]);
explain $graph->as_ascii;

can_ok 'Class::Sniff', 'new_from_namespace';
ok @sniffs = Class::Sniff->new_from_namespace({namespace => qr/rand/}),
    '... and calling it with a regex should succeed';
is scalar(@sniffs), 2,
  '... returning the correct number of Class::Sniff objects';
$graph = $sniffs[0]->combine_graphs(@sniffs[1..$#sniffs]);
explain $graph->as_ascii;