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

=doc

Tests for new property features.  These should probably go into 4.t and 5.t,
but the test suite is a mess of order-of-operations spaghetti tests and i
don't really want to mess with that.  Someday we'll have to overhaul this
suite, but it's late on Sunday night and i don't have the energy to fix what
ain't really broke.

This stuff makes Glib::Object::Subclass's GET_PROPERTY()/SET_PROPERTY()
replacements unnecessary.  Any ideas how to obsolete its new() replacement?

=cut

use Test::More tests => 54;
use Glib ':constants';
use Data::Dumper;
use strict;

# we'll test, for paranoia's sake, that things work the same both with and
# without Glib::Object::Subclass; to simplify things, let's use the exact
# same list of properties for both.  however, since the GObjects take
# ownership of the pspecs, we can't share them.  just use the same code
# to create them:
sub make_properties {
    # a basic one
    Glib::ParamSpec->string ('name', '', '', 'Joe', G_PARAM_READWRITE),
    # now with the new explicit handler syntax:
    {
        # with no handlers, this is the same as not using the hash
        pspec => Glib::ParamSpec->string ('middle', '', '', 'Momma',
                                          G_PARAM_READWRITE),
    },
    {
        pspec => Glib::ParamSpec->string ('nickname', '', '', 'Jimmy-John',
                                          G_PARAM_READWRITE),
        get => sub { ok(1, 'explicit getter for nickname');
                     $_[0]->{nickname} },
        set => sub { ok(1, 'explicit setter for nickname');
                     $_[0]->{nickname} = $_[1] },
    },
    {
        # if you leave out a getter, you get the default behavior
        pspec => Glib::ParamSpec->string ('surname', '', '', 'Jones',
                                          G_PARAM_READWRITE),
        set => sub { ok(1, 'explicit setter for surname');
                     $_[0]->{surname} = $_[1] },
    },
    {
        # same for leaving out a setter
        pspec => Glib::ParamSpec->string ('title', '', '', 'Mr',
                                          G_PARAM_READWRITE),
        get => sub { ok(1, 'explicit getter for title');
                     $_[0]->{title} },
    },
};


# create a new object type by hand (no Glib::Object::Subclass)

Glib::Type->register_object ('Glib::Object', 'Foo',
                             properties => [ &make_properties ]);


# now create one with Subclass, with the same properties.
package Bar;
use Glib::Object::Subclass 'Glib::Object',
    properties => [ &main::make_properties ];

package main;

sub prop_names {
	map { UNIVERSAL::isa ($_, 'Glib::ParamSpec')
	      ? $_->get_name
	      : $_->{pspec}->get_name
	} @_
}
sub Glib::Object::_list_property_names {
	prop_names $_[0]->list_properties
}
sub default_values {
	map { $_->get_default_value } $_[0]->list_properties
}


my @names = prop_names &make_properties;


# start tests

is_deeply ([prop_names (Foo->list_properties)], \@names,
	   'props created correctly for Foo');
my $foo = Foo->new;
isa_ok ($foo, 'Foo', 'it\'s a Foo');
is (scalar keys %$foo, 0, 'new Foo has no keys');

# initially all props should have all default values, except for the ones
# with explicit getters, as the explicit getters don't handle default values.
my @initial_values = default_values ('Foo');
$initial_values[2] = undef;
$initial_values[4] = undef;
my @values = $foo->get (@names);
is_deeply ([$foo->get (@names)], \@initial_values,
           'all defaults except for explicit ones');
is (scalar keys %$foo, 0, 'Foo still has no keys after get');

my @default_values = default_values ('Foo');
$foo->set (map { $names[$_], $default_values[$_] } 0..$#names);
is (scalar keys %$foo, 5, 'new Foo has keys after setting');
is_deeply ([ map {$foo->{$_}} @names ], [ @default_values ],
           'and they have values');

# now add a GET_PROPERTY and SET_PROPERTY that will be called when no
# explicit ones are supplied.
sub get_property {
	ok (1, 'fallback GET_PROPERTY called');
	return 'fallback';
}
sub set_property {
	ok (1, 'fallback SET_PROPERTY called');
	$_[0]->{$_[1]->get_name} = 'fallback';
}
{
no warnings;
*Foo::GET_PROPERTY = \&get_property;
*Foo::SET_PROPERTY = \&set_property;
}

# start over.
$foo = Foo->new;
isa_ok ($foo, 'Foo', 'it\'s a Foo');
is (scalar keys %$foo, 0, 'new Foo has no keys');

# with the overrides in place, none of the implicit keys will have values
# in get, because Subclass's GET doesn't handle defaults.
my @expected = map { defined $_ ? 'fallback' : undef } @initial_values;
@values = $foo->get (@names);
is_deeply ([$foo->get (@names)], \@expected,
           'fallback called for implicit getters');
is (scalar keys %$foo, 0, 'Foo still has no keys after get');

@expected = @default_values;
$expected[0] = 'fallback';
$expected[1] = 'fallback';
$expected[4] = 'fallback';
$foo->set (map { $names[$_], $default_values[$_] } 0..$#names);
is (scalar keys %$foo, 5, 'new Foo has keys after setting');
is_deeply ([ map {$foo->{$_}} @names ], [ @expected ],
           'and they have values');




#
# now verify that Subclass still works as expected.
#

my $bar = Bar->new;
is (scalar keys %$bar, 0, 'bar has no keys on creation');
@expected = @default_values;
$expected[2] = undef;
$expected[4] = undef;
is_deeply ([$bar->get (@names)], \@expected,
           'Subclass works just like registering by hand');
$bar->set (map { $names[$_], $default_values[$_] } 0..$#names);
is (scalar keys %$bar, 5, 'new Foo has keys after setting');
is_deeply ([ map {$bar->{$_}} @names ], [ @default_values ],
           'and they have values');




{
  # Prior to 1.240 a subclass of a class with a pspec/get/set did not reach
  # the specified get/set funcs.

  my @getter_args;
  my @setter_args;
  {
    package BaseGetSet;
    use Glib::Object::Subclass
      'Glib::Object',
      properties => [
		     {
		      pspec => Glib::ParamSpec->string ('my-prop',
							'My-Prop',
							'Blurb one',
							'default one',
							['readable','writable']),
		      get => sub {
			@getter_args = @_;
		      },
		      set => sub {
			@setter_args = @_;
		      },
		     },
		    ];
  }
  {
    package SubGetSet;
    use Glib::Object::Subclass 'BaseGetSet';
  }
  my $obj = SubGetSet->new;

  @getter_args = ();
  @setter_args = ();
  $obj->get ('my-prop');
  is_deeply (\@getter_args, [$obj], 'my-prop reaches BaseGetSet');
  is_deeply (\@setter_args, [],     'my-prop reaches BaseGetSet');

  @getter_args = ();
  @setter_args = ();
  $obj->set (my_prop => 'zzz');
  is_deeply (\@getter_args, [],           'my-prop reaches BaseGetSet');
  is_deeply (\@setter_args, [$obj,'zzz'], 'my-prop reaches BaseGetSet');
}


{
  # Prior to 1.240 a class with a pspec/get/set which is subclassed with
  # another separate pspec/get/set property called to the subclass get/set
  # funcs, not the superclass ones.

  my @baseone_getter_args;
  my @baseone_setter_args;
  {
    package BaseOne;
    use Glib::Object::Subclass
      'Glib::Object',
      properties => [
		     {
		      pspec => Glib::ParamSpec->string ('prop-one',
							'Prop-One',
							'Blurb one',
							'default one',
							['readable','writable']),
		      get => sub {
			@baseone_getter_args = @_;
		      },
		      set => sub {
			# Test::More::diag('baseone setter');
			@baseone_setter_args = @_;
		      },
		     },
		    ];
  }
  my @subtwo_getter_args;
  my @subtwo_setter_args;
  {
    package SubTwo;
    use Glib::Object::Subclass
      'BaseOne',
      properties => [
		     {
		      pspec => Glib::ParamSpec->string ('prop-two',
							'Prop-Two',
							'Blurb two',
							'default two',
							['readable','writable']),
		      get => sub {
			@subtwo_getter_args = @_;
		      },
		      set => sub {
			# Test::More::diag('subtwo setter');
			@subtwo_setter_args = @_;
		      },
		     },
		    ];
  }
  my $obj = SubTwo->new;

  @baseone_getter_args = ();
  @subtwo_getter_args = ();
  $obj->get ('prop-two');
  is_deeply (\@baseone_getter_args, [],     'prop-two goes to subtwo');
  is_deeply (\@subtwo_getter_args,  [$obj], 'prop-two goes to subtwo');

  @baseone_getter_args = ();
  @subtwo_getter_args = ();
  $obj->get ('prop-one');
  is_deeply (\@baseone_getter_args, [$obj], 'prop-one goes to baseone');
  is_deeply (\@subtwo_getter_args,  [],     'prop-one goes to baseone');


  @baseone_setter_args = ();
  @subtwo_setter_args = ();
  $obj->set (prop_two => 'xyz');
  is_deeply (\@baseone_setter_args, [],           'prop-two goes to subtwo');
  is_deeply (\@subtwo_setter_args,  [$obj,'xyz'], 'prop-two goes to subtwo');

  @baseone_setter_args = ();
  @subtwo_setter_args = ();
  $obj->set (prop_one => 'abc');
  is_deeply (\@baseone_setter_args, [$obj,'abc'], 'prop-one goes to baseone');
  is_deeply (\@subtwo_setter_args,  [],           'prop-one goes to baseone');
}