#!/usr/bin/perl

package t::unhandled_rejection;

use strict;
use warnings;

use Test::More;
use Test::Deep;
use Test::FailWarnings;
use Promise::XS;

# should not warn because catch() silences
{
    my $d = Promise::XS::deferred();

    my $p = $d->promise()->catch( sub { } );

    $d->reject("nonono");
}

{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        $d->reject("nonono");

        $d->clear_unhandled_rejection();
    }

    is_deeply(\@w, [], 'no warning after clear_unhandled_rejection()');

    {
        my $d = Promise::XS::deferred();

        $d->reject("nonono");
    }

    cmp_deeply(\@w, [ re( qr<nono> ) ], 'warning normally');
}

# should warn because finally() rejects
{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        my $p = $d->promise();

        $p->then( sub {
            die "nonono";
        } );

        $d->resolve(234);
    }

    cmp_deeply(
        \@w,
        [ re( qr<nonono> ) ],
        'die() in then() triggers warning when promise was never an SV',
    ) or diag explain \@w;
}

{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        my $p = $d->promise();

        my @caught;

        my $p2 = $p->then( sub {
            return Promise::XS::rejected(666);
        } )->catch( sub {
            push @caught, shift;
        } );

        $d->resolve(234);

        is_deeply(\@caught, [666], 'returned rejection caught');
    }

    cmp_deeply(
        \@w,
        [ ],
        'returned rejected() propagates as expected, doesn’t trigger unhandled warning',
    );
}

{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        my $p = $d->promise();

        my $p2 = $p->then( sub {
            die "nonono";
        } );

        undef $p2;

        $d->resolve(234);
    }

    cmp_deeply(
        \@w,
        [ re( qr<nonono> ) ],
        'die() in then() triggers warning when promise was an SV that’s GCed early',
    );
}

{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        my $p = $d->promise();

        $p->then( sub {
            return Promise::XS::rejected(666);
        } );

        $d->resolve(234);
    }

    cmp_deeply(
        \@w,
        [ re( qr<666> ) ],
        'return rejected in then() triggers warning when promise itself never was an SV',
    );
}

{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        $d->reject("nonono");
    }

    cmp_deeply(
        \@w,
        [ re( qr/nonono/ ) ],
        'warning if there was never a perl-ified promise',
    ) or diag explain \@w;
}

# should warn because $d->promise() is uncaught
{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        # The finally() creates a separate result.

        my $p = $d->promise()->finally( sub { } );
        $d->reject("nonono");
    }

    cmp_deeply(
        \@w,
        [ re( qr<nonono> ) ],
        'rejection with finally but no catch triggers 1 warning',
    ) or diag explain \@w;
}

# should warn because finally() gets its own result
{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        my $p = $d->promise();

        my $f = $p->finally( sub { } );

        $p->catch( sub { } );

        $d->reject("nonono");
    }

    cmp_deeply(
        \@w,
        [ re( qr<nonono> ) ],
        'rejected finally is uncaught, but rejection is caught later',
    );
}

# var p = Promise.reject(789); var p2 = p.catch( () => {} ); p.finally( () => {} )
{
    my @w;

    {
        local $SIG{'__WARN__'} = sub { push @w, @_ };

        my $p = Promise::XS::rejected(789);

        my $p2 = $p->catch( sub { } );

        $p->finally( sub { } );
    }

    cmp_deeply(
        \@w,
        [ re( qr<789> ) ],
        'rejected finally (not SV-ified) is uncaught - triggers warning',
    );
}

# var p = Promise.reject(789); var p2 = p.catch( () => {} ); p.finally( () => {} )
{
    my @w;

    {
        local $SIG{'__WARN__'} = sub { push @w, @_ };

        my $p = Promise::XS::rejected(789);

        my $p2 = $p->catch( sub { } );

        my $p3 = $p->finally( sub { } );

        1;
    }

    cmp_deeply(
        \@w,
        [ re( qr<789> ) ],
        'rejected finally is SV-ified but uncaught - triggers warning',
    );
}

# should NOT warn because finally() rejection is caught
{
    my @w;
    local $SIG{'__WARN__'} = sub { push @w, @_ };

    {
        my $d = Promise::XS::deferred();

        my $p = $d->promise();

        my $f = $p->finally( sub { } )->catch( sub { } );

        $p->catch( sub { } );

        $d->reject("awful");
    }

    cmp_deeply(
        \@w,
        [],
        'no warning when finally passthrough rejection is caught',
    ) or diag explain \@w;
}

#----------------------------------------------------------------------

{
    my $d = Promise::XS::deferred();

    my $p = $d->resolve(123)->promise()->then( sub {
        my ($value) = @_;

        return Promise::XS::rejected( { message => 'oh my god', value => $value } );
    })->catch(sub {
        my ($reason) = @_;
        return $reason;
    });

    my $got;

    $p->then( sub { $got = shift } );

    is_deeply $got, { message => 'oh my god', value => 123 }, 'got expected';
}

#----------------------------------------------------------------------

{
    my $d = Promise::XS::deferred();

    my $p = $d->resolve(123)->promise()->then( sub {
        my ($value) = @_;

        return bless [], 'ForeignRejectedPromise';
    })->catch(sub {
        my ($reason) = @_;
        return $reason;
    });

    my $got;

    $p->then( sub { $got = shift } );

    is_deeply $got, 'ForeignRejectedPromise', 'got expected from foreign rejected';
}

done_testing();

#----------------------------------------------------------------------

package ForeignRejectedPromise;

sub then {
    my ($self, $on_res, $on_rej) = @_;

    $on_rej->(ref $self);

    return $self;
}

1;
