Test that accessor works with lazy generation
[gitmo/Moose.git] / t / 070_native_traits / 050_trait_hash.t
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5
6 use lib 't/lib';
7
8 use Moose ();
9 use Moose::Util::TypeConstraints;
10 use NoInlineAttribute;
11 use Test::Fatal;
12 use Test::More;
13 use Test::Moose;
14
15 {
16     my %handles = (
17         option_accessor  => 'accessor',
18         quantity         => [ accessor => 'quantity' ],
19         clear_options    => 'clear',
20         num_options      => 'count',
21         delete_option    => 'delete',
22         is_defined       => 'defined',
23         options_elements => 'elements',
24         has_option       => 'exists',
25         get_option       => 'get',
26         has_no_options   => 'is_empty',
27         keys             => 'keys',
28         key_value        => 'kv',
29         set_option       => 'set',
30     );
31
32     my $name = 'Foo1';
33
34     sub build_class {
35         my %attr = @_;
36
37         my $class = Moose::Meta::Class->create(
38             $name++,
39             superclasses => ['Moose::Object'],
40         );
41
42         my @traits = 'Hash';
43         push @traits, 'NoInlineAttribute'
44             if delete $attr{no_inline};
45
46         $class->add_attribute(
47             options => (
48                 traits  => \@traits,
49                 is      => 'ro',
50                 isa     => 'HashRef[Str]',
51                 default => sub { {} },
52                 handles => \%handles,
53                 clearer => '_clear_options',
54                 %attr,
55             ),
56         );
57
58         return ( $class->name, \%handles );
59     }
60 }
61
62 {
63     run_tests(build_class);
64     run_tests( build_class( lazy => 1, default => sub { { x => 1 } } ) );
65     run_tests( build_class( trigger => sub { } ) );
66     run_tests( build_class( no_inline => 1 ) );
67
68     # Will force the inlining code to check the entire hashref when it is modified.
69     subtype 'MyHashRef', as 'HashRef[Str]', where { 1 };
70
71     run_tests( build_class( isa => 'MyHashRef' ) );
72
73     coerce 'MyHashRef', from 'HashRef', via { $_ };
74
75     run_tests( build_class( isa => 'MyHashRef', coerce => 1 ) );
76 }
77
78 sub run_tests {
79     my ( $class, $handles ) = @_;
80
81     can_ok( $class, $_ ) for sort keys %{$handles};
82
83     with_immutable {
84         my $obj = $class->new( options => {} );
85
86         ok( $obj->has_no_options, '... we have no options' );
87         is( $obj->num_options, 0, '... we have no options' );
88
89         is_deeply( $obj->options, {}, '... no options yet' );
90         ok( !$obj->has_option('foo'), '... we have no foo option' );
91
92         is( exception {
93             is(
94                 $obj->set_option( foo => 'bar' ),
95                 'bar',
96                 'set return single new value in scalar context'
97             );
98         }, undef, '... set the option okay' );
99
100         ok( $obj->is_defined('foo'), '... foo is defined' );
101
102         ok( !$obj->has_no_options, '... we have options' );
103         is( $obj->num_options, 1, '... we have 1 option(s)' );
104         ok( $obj->has_option('foo'), '... we have a foo option' );
105         is_deeply( $obj->options, { foo => 'bar' }, '... got options now' );
106
107         is( exception {
108             $obj->set_option( bar => 'baz' );
109         }, undef, '... set the option okay' );
110
111         is( $obj->num_options, 2, '... we have 2 option(s)' );
112         is_deeply(
113             $obj->options, { foo => 'bar', bar => 'baz' },
114             '... got more options now'
115         );
116
117         is( $obj->get_option('foo'), 'bar', '... got the right option' );
118
119         is_deeply(
120             [ $obj->get_option(qw(foo bar)) ], [qw(bar baz)],
121             "get multiple options at once"
122         );
123
124         is(
125             scalar( $obj->get_option(qw( foo bar)) ), "baz",
126             '... got last option in scalar context'
127         );
128
129         is( exception {
130             $obj->set_option( oink => "blah", xxy => "flop" );
131         }, undef, '... set the option okay' );
132
133         is( $obj->num_options, 4, "4 options" );
134         is_deeply(
135             [ $obj->get_option(qw(foo bar oink xxy)) ],
136             [qw(bar baz blah flop)], "get multiple options at once"
137         );
138
139         is( exception {
140             is( scalar $obj->delete_option('bar'), 'baz',
141                 'delete returns deleted value' );
142         }, undef, '... deleted the option okay' );
143
144         is( exception {
145             is_deeply(
146                 [ $obj->delete_option( 'oink', 'xxy' ) ],
147                 [ 'blah', 'flop' ],
148                 'delete returns all deleted values in list context'
149             );
150         }, undef, '... deleted multiple option okay' );
151
152         is( $obj->num_options, 1, '... we have 1 option(s)' );
153         is_deeply(
154             $obj->options, { foo => 'bar' },
155             '... got more options now'
156         );
157
158         $obj->clear_options;
159
160         is_deeply( $obj->options, {}, "... cleared options" );
161
162         is( exception {
163             $obj->quantity(4);
164         }, undef, '... options added okay with defaults' );
165
166         is( $obj->quantity, 4, 'reader part of curried accessor works' );
167
168         is_deeply(
169             $obj->options, { quantity => 4 },
170             '... returns what we expect'
171         );
172
173         is( exception {
174             $class->new( options => { foo => 'BAR' } );
175         }, undef, '... good constructor params' );
176
177         isnt( exception {
178             $obj->set_option( bar => {} );
179         }, undef, '... could not add a hash ref where an string is expected' );
180
181         isnt( exception {
182             $class->new( options => { foo => [] } );
183         }, undef, '... bad constructor params' );
184
185         is_deeply(
186             [ $obj->set_option( oink => "blah", xxy => "flop" ) ],
187             [ 'blah', 'flop' ],
188             'set returns newly set values in order of keys provided'
189         );
190
191         my @key_value = sort { $a->[0] cmp $b->[0] } $obj->key_value;
192         is_deeply(
193             \@key_value,
194             [
195                 sort { $a->[0] cmp $b->[0] }[ 'xxy', 'flop' ],
196                 [ 'quantity', 4 ],
197                 [ 'oink',     'blah' ]
198             ],
199             '... got the right key value pairs'
200             )
201             or do {
202             require Data::Dumper;
203             diag( Data::Dumper::Dumper( \@key_value ) );
204             };
205
206         my %options_elements = $obj->options_elements;
207         is_deeply(
208             \%options_elements, {
209                 'oink'     => 'blah',
210                 'quantity' => 4,
211                 'xxy'      => 'flop'
212             },
213             '... got the right hash elements'
214         );
215
216         if ( $class->meta->get_attribute('options')->is_lazy ) {
217             my $obj = $class->new;
218
219             $obj->set_option( y => 2 );
220
221             is_deeply(
222                 $obj->options, { x => 1, y => 2 },
223                 'set_option with lazy default'
224             );
225
226             $obj->_clear_options;
227
228             ok(
229                 $obj->has_option('x'),
230                 'key for x exists - lazy default'
231             );
232
233             $obj->_clear_options;
234
235             ok(
236                 $obj->is_defined('x'),
237                 'key for x is defined - lazy default'
238             );
239
240             $obj->_clear_options;
241
242             is_deeply(
243                 [ $obj->key_value ],
244                 [ [ x => 1 ] ],
245                 'kv returns lazy default'
246             );
247
248
249             $obj->_clear_options;
250
251             $obj->option_accessor( y => 2 );
252
253             is_deeply(
254                 [ sort $obj->keys ],
255                 [ 'x', 'y' ],
256                 'accessor triggers lazy default generator'
257             );
258         }
259     }
260     $class;
261 }
262
263 done_testing;