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

use strict;
BEGIN {
	$|  = 1;
	$^W = 1;
}

use Test::More tests => 24;
use Test::NoWarnings;
use Test::Exception;
use Aspect;
use Aspect::Library::Listenable;

# Set up the Listenable relationship types
aspect Listenable => (
	Erase => call "Foo::set_erased",
);
aspect Listenable => (
	Color => call "Foo::set_color",
	color => 'get_color',
);

# Create a temporary lexical aspect to make sure we create lexical advice
SCOPE: {
	my $lexical = aspect Listenable => (
		Foo => call 'Baz::one',
	);
	isa_ok( $lexical, 'Aspect::Library::Listenable' );
}





######################################################################
# Main Tests

SCOPE: {
	my $has_been_called = 0;
	my $point = Foo->new;
	add_listener( $point, Erase => my $listener = sub { $has_been_called = 1 } );
	$point->set_erased;
	ok( $point->get_erased, 'has been erased' );
	ok( $has_been_called, 'has been called' );
	remove_listener( $point, Erase => $listener );
}

SCOPE: {
	my $has_been_called1 = 0;
	my $has_been_called2 = 0;
	my $point = Foo->new;
	add_listener( $point, Erase => my $listener1 = sub { $has_been_called1 = 1 } );
	add_listener( $point, Erase => my $listener2 = sub { $has_been_called2 = 1 } );
	$point->set_erased;
	ok( $has_been_called1, 'listener 1' );
	ok( $has_been_called2, 'listener 2' );
	remove_listener( $point, Erase => $listener1 );
	remove_listener( $point, Erase => $listener2 );
}

SCOPE: {
	my $has_been_called1 = 0;
	my $has_been_called2 = 0;
	my $point1 = Foo->new;
	my $point2 = Foo->new;
	add_listener( $point1, Erase => my $listener1 = sub { $has_been_called1 = 1 } );
	add_listener( $point2, Erase => my $listener2 = sub { $has_been_called2 = 1 } );
	$point2->set_erased;
	ok( !$point1->get_erased, 'point 1'  );
	ok( $point2->get_erased, 'point 2'   );
	ok( !$has_been_called1, 'listener 1' );
	ok( $has_been_called2, 'listener 2'  );
	remove_listener( $point1, Erase => $listener1 );
	remove_listener( $point2, Erase => $listener2 );
}

SCOPE: {
	my $has_been_called1 = 0;
	my $has_been_called2 = 0;
	my $point = Foo->new;
	add_listener( $point, Erase => my $listener1 = sub { $has_been_called1 = 1 } );
	add_listener( $point, Erase => my $listener2 = sub { $has_been_called2 = 1 } );
	remove_listener( $point, Erase => $listener1 );
	$point->set_erased;
	ok( !$has_been_called1, 'listener 1' );
	ok( $has_been_called2, 'listener 2'  );
	remove_listener( $point, Erase => $listener2 );
}

SCOPE: {
	my $event = 0;
	my $point = Foo->new;
	add_listener( $point, Color => my $listener = sub { $event = shift } );
	$point->set_color('red');
	is( $point->get_color, 'red', 'point color' );
	ok( $event, 'event fired' );
	is( ref $event, 'Aspect::Library::Listenable::Event', 'event class' );
	is( $event->name, 'Color', 'name' );
	is( $event->source, $point, 'source' );
	is( $event->color, 'red', 'color' );
	is( $event->old_color, 'blue', 'old_color' );
	is_deeply( $event->params, [ 'red' ], 'args' );
	remove_listener( $point, Color => $listener );
}

SCOPE: {
	my $hasnt_been_called = 1;
	my $point = Foo->new;
	add_listener( $point, Color => my $listener = sub { $hasnt_been_called = 0 } );
	$point->set_color('blue');
	ok( $hasnt_been_called );
	remove_listener( $point, Color => $listener );
}

SCOPE: {
	my $listenable = bless [], 'SomePackage';
	throws_ok(
		sub { add_listener( $listenable, Event => sub {} ) },
		qr/not a hash based object/,
	);
}

SCOPE: {
	my $point = Foo->new;
	add_listener( $point, Erase => [do_call =>
		my $listener = Bar->new,
		[qw(source)],
	] );
	ok( !$listener->has_been_called, 'before erased' );
	$point->set_erased;
	is( $listener->has_been_called, $point, 'after erased' );
	remove_listener( $point, Erase => $listener );
}





######################################################################
# Support Classes

package Foo;

sub new {
	bless {
		color  => 'blue',
		erased => 0,
	}, shift;
}

sub get_erased {
	shift->{erased}
}

sub set_erased {
	shift->{erased} = 1;
}

sub get_color {
	shift->{color};
}

sub set_color {
	shift->{color} = pop;
}

package Bar;

sub new {
	bless {
		has_been_called => 0,
	}, shift;
}

sub do_call {
	shift->{has_been_called} = pop;
}

sub has_been_called {
	shift->{has_been_called};
}

package Baz;

sub one {
	return 'one';
}