clean undef-tolerant and undefined args in BUILDARGS
[gitmo/MooseX-UndefTolerant.git] / t / lib / ConstructorTests.pm
CommitLineData
37acc770 1package # hide from PAUSE
2 ConstructorTests;
3
4{
15d56027 5 package # hide from PAUSE
6 Base;
37acc770 7 use Moose;
8
15d56027 9 # save a before and after copy of arguments for later testing.
10 has $_ => (
11 isa => 'HashRef',
12 writer => '_set_' . $_,
13 lazy => 1,
14 default => sub { die 'in default' },
15 traits => ['Hash'],
16 handles => { $_ => 'elements' },
17 ) foreach (qw(args_orig args_final));
18 around BUILDARGS => sub {
19 my $orig = shift;
20 my $class = shift;
21
22 my %original_args = @_;
23 my $args = $class->$orig(@_);
24 return {
25 %$args,
26 args_orig => \%original_args,
27 };
28 };
29
30 # we save the final arg list here rather than in BUILDARGS, as we can't guarantee
31 # the order of role application in the two method modifications of
32 # BUILDARGS (here, and in MooseX::UndefTolerant::Object)
33 sub BUILD
34 {
35 my ($self, $args) = @_;
36 my %args = %$args;
37 delete $args{args_orig};
38 $self->_set_args_final(\%args);
39 }
40}
41
42{
43 package # hide from PAUSE
44 Foo;
45 use Moose;
46 extends 'Base';
47
48 # attrs that did not like undefs until we applied this trait
49 our @newly_tolerant_attrs = qw(attr1);
37acc770 50 has 'attr1' => (
51 traits => [ qw(MooseX::UndefTolerant::Attribute)],
52 is => 'ro',
53 isa => 'Num',
54 predicate => 'has_attr1',
55 );
56 has 'attr2' => (
57 is => 'ro',
58 isa => 'Num',
59 predicate => 'has_attr2',
60 );
61 has 'attr3' => (
62 is => 'ro',
63 isa => 'Maybe[Num]',
64 predicate => 'has_attr3',
65 );
66}
67
68{
15d56027 69 package # hide from PAUSE
70 Bar;
37acc770 71 use Moose;
15d56027 72
73 extends 'Base';
37acc770 74 use MooseX::UndefTolerant;
75
15d56027 76 # attrs that did not like undefs until we applied this trait
77 our @newly_tolerant_attrs = qw(attr1 attr2);
37acc770 78 has 'attr1' => (
79 is => 'ro',
80 isa => 'Num',
81 predicate => 'has_attr1',
82 );
83 has 'attr2' => (
84 is => 'ro',
85 isa => 'Num',
86 predicate => 'has_attr2',
87 );
88 has 'attr3' => (
89 is => 'ro',
90 isa => 'Maybe[Num]',
91 predicate => 'has_attr3',
92 );
93}
94
95package # hide from PAUSE
96 ConstructorTests;
97
98use strict;
99use warnings;
100
101use Test::More;
102use Test::Fatal;
15d56027 103use Scalar::Util 'blessed';
104use List::MoreUtils 'any';
105
106# checks all values passing through BUILDARGS to confirm they were handled
107# appropriately:
108# - defined fields left alone
109# - undefined fields cleaned iff the type constraint would fail and has the
110# UndefTolerant trait
111sub attrs_cleaned
112{
113 my $obj = shift;
114
115 no strict 'refs';
116 my @newly_tolerant_attrs = @{blessed($obj) . '::newly_tolerant_attrs'};
117 my %original_args = $obj->args_orig;
118 my %final_args = $obj->args_final;
119
120 foreach my $attr (qw(attr1 attr2 attr3))
121 {
122 local $TODO;
123 $TODO = 'BUILDARGS cannot be cleaned if the entire class is not undef-tolerant; see CAVEATS'
124 if $obj->meta->is_mutable and not $obj->does('MooseX::UndefTolerant::Class')
125 and exists $original_args{$attr}
126 and not defined $original_args{$attr}
127 and any { $_ eq $attr } @newly_tolerant_attrs;
128
129 is(
130 # actual state of arg after passing through BUILDARGS
131 !exists($final_args{$attr})
132 ? 'not passed'
133 : defined($final_args{$attr})
134 ? 'defined'
135 : 'undefined',
136 # expected state of arg after passing through BUILDARGS
137 do {
138 !exists($original_args{$attr})
139 ? 'not passed'
140 : defined($original_args{$attr})
141 ? 'defined'
142 : (any { $_ eq $attr } @newly_tolerant_attrs)
143 ? 'not passed' # these attrs were cleaned out
144 : 'undefined';
145 },
146 'constructor argument "' . $attr . '" is updated appropriately in BUILDARGS',
147 );
148 }
149}
37acc770 150
151sub do_tests
152{
4f5c4e45 153 note 'Testing ', (Foo->meta->is_immutable ? 'im' : '') . 'mutable ',
154 'class with a single UndefTolerant attribute';
37acc770 155 {
156 my $obj = Foo->new;
15d56027 157 attrs_cleaned($obj);
37acc770 158 ok(!$obj->has_attr1, 'attr1 has no value before it is assigned');
159 ok(!$obj->has_attr2, 'attr2 has no value before it is assigned');
160 ok(!$obj->has_attr3, 'attr3 has no value before it is assigned');
161 }
162
163 TODO: {
164 local $TODO;
165 $TODO = 'some immutable cases are not handled yet; see CAVEATS' if Foo->meta->is_immutable;
166 is(
167 exception {
37acc770 168 my $obj = Foo->new(attr1 => undef);
15d56027 169 attrs_cleaned($obj);
170
37acc770 171 ok(!$obj->has_attr1, 'UT attr1 has no value when assigned undef in constructor');
172 like(
173 exception { $obj = Foo->new(attr2 => undef) },
174 qr/\QAttribute (attr2) does not pass the type constraint because: Validation failed for 'Num' with value undef\E/,
175 'But assigning undef to attr2 generates a type constraint error');
176
177 is (exception { $obj = Foo->new(attr3 => undef) }, undef,
178 'assigning undef to attr3 is acceptable');
f6bb95bd 179 ok($obj->has_attr3, 'attr3 still has a value');
180 is($obj->attr3, undef, '...which is undef, when assigned undef in constructor');
15d56027 181 attrs_cleaned($obj);
37acc770 182 },
183 undef,
4f5c4e45 184 'successfully tested spot-application of UT trait in '
185 . (Foo->meta->is_immutable ? 'im' : '') . 'mutable classes',
37acc770 186 );
187 }
188
189 {
190 my $obj = Foo->new(attr1 => 1234, attr2 => 5678, attr3 => 9012);
15d56027 191 attrs_cleaned($obj);
37acc770 192 is($obj->attr1, 1234, 'assigning a defined value during construction works as normal');
193 ok($obj->has_attr1, '...and the predicate returns true as normal');
194
195 is($obj->attr2, 5678, 'assigning a defined value during construction works as normal');
196 ok($obj->has_attr2, '...and the predicate returns true as normal');
197
198 is($obj->attr3, 9012, 'assigning a defined value during construction works as normal');
199 ok($obj->has_attr3, '...and the predicate returns true as normal');
200 }
201
37acc770 202 note '';
4f5c4e45 203 note 'Testing class with the entire ',
aeb57613 204 (Bar->meta->is_immutable ? 'im' : '') . 'mutable ',
4f5c4e45 205 'class being UndefTolerant';
37acc770 206 {
207 my $obj = Bar->new;
15d56027 208 attrs_cleaned($obj);
37acc770 209 ok(!$obj->has_attr1, 'attr1 has no value before it is assigned');
210 ok(!$obj->has_attr2, 'attr2 has no value before it is assigned');
211 ok(!$obj->has_attr3, 'attr3 has no value before it is assigned');
212 }
213
214 {
215 my $obj = Bar->new(attr1 => undef);
15d56027 216 attrs_cleaned($obj);
37acc770 217 ok(!$obj->has_attr1, 'attr1 has no value when assigned undef in constructor');
218 # note this test differs from the Foo case above
219 is (exception { $obj = Bar->new(attr2 => undef) }, undef,
220 'assigning undef to attr2 does not produce an error');
15d56027 221 attrs_cleaned($obj);
37acc770 222 ok(!$obj->has_attr2, 'attr2 has no value when assigned undef in constructor');
223
f6bb95bd 224 is( exception { $obj = Bar->new(attr3 => undef) }, undef,
37acc770 225 'assigning undef to attr3 is acceptable');
f6bb95bd 226 ok($obj->has_attr3, 'attr3 still has a value');
227 is($obj->attr3, undef, '...which is undef, when assigned undef in constructor');
15d56027 228 attrs_cleaned($obj);
37acc770 229 }
230
231 {
232 my $obj = Bar->new(attr1 => 1234, attr2 => 5678, attr3 => 9012);
15d56027 233 attrs_cleaned($obj);
37acc770 234 is($obj->attr1, 1234, 'assigning a defined value during construction works as normal');
235 ok($obj->has_attr1, '...and the predicate returns true as normal');
236
237 is($obj->attr2, 5678, 'assigning a defined value during construction works as normal');
238 ok($obj->has_attr2, '...and the predicate returns true as normal');
239
240 is($obj->attr3, 9012, 'assigning a defined value during construction works as normal');
241 ok($obj->has_attr3, '...and the predicate returns true as normal');
242 }
243}
244
2451;