package Test::TypeTiny;

use strict;
use warnings;

use Test::More qw();
use Scalar::Util qw(blessed);
use Types::TypeTiny qw(to_TypeTiny);

require Exporter::Tiny;
our @ISA = 'Exporter::Tiny';

BEGIN {
	*EXTENDED_TESTING = $ENV{EXTENDED_TESTING} ? sub(){!!1} : sub(){!!0};
};

our $AUTHORITY = 'cpan:TOBYINK';
our $VERSION   = '1.000000';
our @EXPORT    = qw( should_pass should_fail ok_subtype );
our @EXPORT_OK = qw( EXTENDED_TESTING matchfor );

sub matchfor
{
	my @matchers = @_;
	bless \@matchers, do {
		package #
		Test::TypeTiny::Internal::MATCHFOR;
		use overload
			q[==] => 'match',
			q[eq] => 'match',
			q[""] => 'to_string',
			fallback => 1;
		sub to_string {
			$_[0][0]
		}
		sub match {
			my ($self, $e) = @_;
			my $does = Scalar::Util::blessed($e) ? ($e->can('DOES') || $e->can('isa')) : undef;
			for my $s (@$self) {
				return 1 if  ref($s) && $e =~ $s;
				return 1 if !ref($s) && $does && $e->$does($s);
			}
			return;
		}
		__PACKAGE__;
	};
}

sub _mk_message
{
	require Type::Tiny;
	my ($template, $value) = @_;
	sprintf($template, Type::Tiny::_dd($value));
}

sub ok_subtype
{
	my ($type, @s) = @_;
	@_ = (
		not(scalar grep !$_->is_subtype_of($type), @s),
		sprintf("%s subtype: %s", $type, join q[, ], @s),
	);
	goto \&Test::More::ok;
}

eval(EXTENDED_TESTING ? <<'SLOW' : <<'FAST');

sub should_pass
{
	my ($value, $type, $message) = @_;
	
	local $Test::Builder::Level = $Test::Builder::Level + 1;
	$type = to_TypeTiny($type) unless blessed($type) && $type->can("check");
	
	my $strictures = $type->can("_strict_check");
	
	my $test = "Test::Builder"->new->child(
		$message || _mk_message("%s passes type constraint $type", $value),
	);
	$test->plan(tests => ($strictures ? 2 : 1));
	$test->ok(!!$type->check($value), '->check');
	$test->ok(!!$type->_strict_check($value), '->_strict_check') if $strictures;
	$test->finalize;
	return $test->is_passing;
}

sub should_fail
{
	my ($value, $type, $message) = @_;
	$type = to_TypeTiny($type) unless blessed($type) && $type->can("check");
	
	local $Test::Builder::Level = $Test::Builder::Level + 1;
	
	my $strictures = $type->can("_strict_check");
	
	my $test = "Test::Builder"->new->child(
		$message || _mk_message("%s fails type constraint $type", $value),
	);
	$test->plan(tests => ($strictures ? 2 : 1));
	$test->ok(!$type->check($value), '->check');
	$test->ok(!$type->_strict_check($value), '->_strict_check') if $strictures;
	$test->finalize;
	return $test->is_passing;
}

SLOW

sub should_pass
{
	my ($value, $type, $message) = @_;
	$type = to_TypeTiny($type) unless blessed($type) && $type->can("check");
	@_ = (
		!!$type->check($value),
		$message || _mk_message("%s passes type constraint $type", $value),
	);
	goto \&Test::More::ok;
}

sub should_fail
{
	my ($value, $type, $message) = @_;
	$type = to_TypeTiny($type) unless blessed($type) && $type->can("check");
	@_ = (
		!$type->check($value),
		$message || _mk_message("%s fails type constraint $type", $value),
	);
	goto \&Test::More::ok;
}

FAST

1;

__END__

=pod

=encoding utf-8

=head1 NAME

Test::TypeTiny - useful functions for testing the efficacy of type constraints

=head1 SYNOPSIS

=for test_synopsis
BEGIN { die "SKIP: uses a module that doesn't exist as an example" };

   use strict;
   use warnings;
   use Test::More;
   use Test::TypeTiny;
   
   use Types::Mine qw(Integer Number);
   
   should_pass(1, Integer);
   should_pass(-1, Integer);
   should_pass(0, Integer);
   should_fail(2.5, Integer);
   
   ok_subtype(Number, Integer);
   
   done_testing;

=head1 STATUS

This module is covered by the
L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.

=head1 DESCRIPTION

L<Test::TypeTiny> provides a few handy functions for testing type constraints.

=head2 Functions

=over

=item C<< should_pass($value, $type, $test_name) >>

=item C<< should_pass($value, $type) >>

Test that passes iff C<< $value >> passes C<< $type->check >>.

=item C<< should_fail($value, $type, $test_name) >>

=item C<< should_fail($value, $type) >>

Test that passes iff C<< $value >> fails C<< $type->check >>.

=item C<< ok_subtype($type, @subtypes) >>

Test that passes iff all C<< @subtypes >> are subtypes of C<< $type >>.

=item C<< EXTENDED_TESTING >>

Exportable boolean constant.

=item C<< matchfor(@things) >>

Assistant for matching exceptions. Not exported by default.
See also L<Test::Fatal::matchfor>.

=back

=head1 ENVIRONMENT

If the C<EXTENDED_TESTING> environment variable is set to true, this
module will promote each C<should_pass> or C<should_fail> test into a
subtest block and test the type constraint in both an inlined and
non-inlined manner.

This variable must be set at compile time (i.e. before this module is
loaded).

=head1 BUGS

Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=Type-Tiny>.

=head1 SEE ALSO

L<Type::Tiny>.

For an alternative to C<should_pass>, see L<Test::Deep::Type> which will
happily accept a Type::Tiny type constraint instead of a MooseX::Types one.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2013-2014 by Toby Inkster.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

