Add note about how extends is necessary for metaclass compat
[gitmo/moose-presentations.git] / moose-class / exercises / t / lib / MooseClass / Tests.pm
CommitLineData
ddd87d75 1package MooseClass::Tests;
2
3use strict;
4use warnings;
5
5cab7e05 6use Lingua::EN::Inflect qw( A PL_N );
ddd87d75 7use Test::More 'no_plan';
8
9sub tests01 {
10 local $Test::Builder::Level = $Test::Builder::Level + 1;
11
12 has_meta('Person');
13
14 check_isa( 'Person', ['Moose::Object'] );
15
ddd87d75 16 has_rw_attr( 'Person', $_ ) for qw( first_name last_name );
17
18 has_method( 'Person', 'full_name' );
19
20 no_droppings('Person');
21 is_immutable('Person');
22
23 person01();
24
25 has_meta('Employee');
26
27 check_isa( 'Employee', [ 'Person', 'Moose::Object' ] );
28
8d1ce1d7 29 has_rw_attr( 'Employee', $_ ) for qw( title salary );
ddd87d75 30 has_ro_attr( 'Employee', 'ssn' );
31
32 has_overridden_method( 'Employee', 'full_name' );
33
34 employee01();
35}
36
5cab7e05 37sub tests02 {
a8c37857 38 tests01();
5cab7e05 39
40 local $Test::Builder::Level = $Test::Builder::Level + 1;
41
42 no_droppings($_) for qw( Printable HasAccount );
43
44 does_role( 'Person', $_ ) for qw( Printable HasAccount );
45 has_method( 'Person', $_ ) for qw( as_string deposit withdraw );
46 has_rw_attr( 'Person', 'balance' );
47
48 does_role( 'Employee', $_ ) for qw( Printable HasAccount );
49
50 person02();
51 employee02();
52}
53
8d1ce1d7 54sub tests03 {
55 {
56 local $Test::Builder::Level = $Test::Builder::Level + 1;
57
58 has_meta('Person');
59 has_meta('Employee');
60
61 has_rw_attr( 'Person', 'title' );
62
63 has_rw_attr( 'Employee', 'title' );
64 has_rw_attr( 'Employee', 'salary_level' );
65 has_ro_attr( 'Employee', 'salary' );
66
67 has_method( 'Employee', '_build_salary' );
68 }
69
70 ok( ! Employee->meta->has_method('full_name'),
71 'Employee no longer implements a full_name method' );
72
73 my $person_title_attr = Person->meta->get_attribute('title');
74 ok( !$person_title_attr->is_required, 'title is not required in Person' );
75 is( $person_title_attr->predicate, 'has_title',
76 'Person title attr has a has_title predicate' );
77 is( $person_title_attr->clearer, 'clear_title',
78 'Person title attr has a clear_title clearer' );
79
80 my $balance_attr = Person->meta->get_attribute('balance');
81 is( $balance_attr->default, 100, 'balance defaults to 100' );
82
83 my $employee_title_attr = Employee->meta->get_attribute('title');
84 is( $employee_title_attr->default, 'Worker',
85 'title defaults to Worker in Employee' );
86
87 my $salary_level_attr = Employee->meta->get_attribute('salary_level');
88 is( $salary_level_attr->default, 1, 'salary_level defaults to 1' );
89
90 my $salary_attr = Employee->meta->get_attribute('salary');
91 ok( !$salary_attr->init_arg, 'no init_arg for salary attribute' );
92 ok( $salary_attr->has_builder, 'salary attr has a builder' );
93
94 person03();
95 employee03();
96}
97
26164c8d 98sub tests04 {
99 {
100 local $Test::Builder::Level = $Test::Builder::Level + 1;
101
538499df 102 has_meta('Document');
103 has_meta('Report');
104 has_meta('TPSReport');
26164c8d 105
538499df 106 no_droppings('Document');
107 no_droppings('Report');
108 no_droppings('TPSReport');
109
110 has_ro_attr( 'Document', $_ ) for qw( title author );
111 has_ro_attr( 'Report', 'summary' );
112 has_ro_attr( 'TPSReport', $_ ) for qw( t p s );
113
114 has_method( 'Document', 'output' );
115 has_augmented_method( 'Report', 'output' );
116 has_augmented_method( 'TPSReport', 'output' );
26164c8d 117 }
118
538499df 119 my $tps = TPSReport->new(
120 title => 'That TPS Report',
121 author => 'Peter Gibbons (for Bill Lumberg)',
122 summary => 'I celebrate his whole collection!',
123 t => 'PC Load Letter',
124 p => 'Swingline',
125 s => 'flair!',
126 );
26164c8d 127
538499df 128 my $output = $tps->output;
129 $output =~ s/\n\n+/\n/g;
26164c8d 130
538499df 131 is( $output, <<'EOF', 'output returns expected report' );
132That TPS Report
133I celebrate his whole collection!
134t: PC Load Letter
135p: Swingline
136s: flair!
137Written by Peter Gibbons (for Bill Lumberg)
138EOF
26164c8d 139}
140
ad648c43 141sub tests05 {
142 {
143 local $Test::Builder::Level = $Test::Builder::Level + 1;
144
145 has_meta('Person');
146 has_meta('Employee');
147 no_droppings('Employee');
148 }
149
150 for my $attr_name ( qw( first_name last_name title ) ) {
151 my $attr = Person->meta->get_attribute($attr_name);
152
153 ok( $attr->has_type_constraint,
154 "Person $attr_name has a type constraint" );
155 is( $attr->type_constraint->name, 'Str',
156 "Person $attr_name type is Str" );
157 }
158
159 {
160 my $salary_level_attr = Employee->meta->get_attribute('salary_level');
161 ok( $salary_level_attr->has_type_constraint,
162 'Employee salary_level has a type constraint' );
163
164 my $tc = $salary_level_attr->type_constraint;
165
166 for my $invalid ( 0, 11, -14, 'foo', undef ) {
167 my $str = defined $invalid ? $invalid : 'undef';
168 ok( ! $tc->check($invalid),
169 "salary_level type rejects invalid value - $str" );
170 }
171
172 for my $valid ( 1..10 ) {
173 ok( $tc->check($valid),
174 "salary_level type accepts valid value - $valid" );
175 }
176 }
177
178 {
179 my $salary_attr = Employee->meta->get_attribute('salary');
180
181 ok( $salary_attr->has_type_constraint,
182 'Employee salary has a type constraint' );
183
184 my $tc = $salary_attr->type_constraint;
185
186 for my $invalid ( 0, -14, 'foo', undef ) {
187 my $str = defined $invalid ? $invalid : 'undef';
188 ok( ! $tc->check($invalid),
189 "salary type rejects invalid value - $str" );
190 }
191
192 for my $valid ( 1, 100_000, 10**10 ) {
193 ok( $tc->check($valid),
194 "salary type accepts valid value - $valid" );
195 }
196 }
197
198 {
199 my $ssn_attr = Employee->meta->get_attribute('ssn');
200
201 ok( $ssn_attr->has_type_constraint,
202 'Employee ssn has a type constraint' );
203
204 my $tc = $ssn_attr->type_constraint;
205
206 for my $invalid ( 0, -14, 'foo', undef, '123-ab-1241', '123456789' ) {
207 my $str = defined $invalid ? $invalid : 'undef';
208 ok( ! $tc->check($invalid),
209 "ssn type rejects invalid value - $str" );
210 }
211
212 for my $valid ( '041-12-1251', '123-45-6789', '926-41-5820' ) {
213 ok( $tc->check($valid),
214 "ssn type accepts valid value - $valid" );
215 }
216 }
217}
218
66b226e5 219sub tests06 {
220 {
221 local $Test::Builder::Level = $Test::Builder::Level + 1;
222
223 has_meta('BankAccount');
224 no_droppings('BankAccount');
225
226 has_rw_attr( 'BankAccount', 'balance' );
227 has_rw_attr( 'BankAccount', 'owner' );
228 has_ro_attr( 'BankAccount', 'history' );
229 }
230
231 my $person_meta = Person->meta;
232 ok( ! $person_meta->has_attribute('balance'),
233 'Person class does not have a balance attribute' );
234
235 my $deposit_meth = $person_meta->get_method('deposit');
236 isa_ok( $deposit_meth, 'Moose::Meta::Method::Delegation' );
237
238 my $withdraw_meth = $person_meta->get_method('withdraw');
239 isa_ok( $withdraw_meth, 'Moose::Meta::Method::Delegation' );
240
241 my $ba_meta = BankAccount->meta;
242 ok( $ba_meta->get_attribute('owner')->is_weak_ref,
243 'owner attribute is a weak ref' );
244
245 person06();
246}
247
248
ddd87d75 249sub has_meta {
250 my $class = shift;
251
252 ok( $class->can('meta'), "$class has a meta() method" )
29ae6919 253 or BAIL_OUT("$class does not have a meta() method (did you forget to 'use Moose'?)");
ddd87d75 254}
255
256sub check_isa {
257 my $class = shift;
258 my $parents = shift;
259
260 my @isa = $class->meta->linearized_isa;
261 shift @isa; # returns $class as the first entry
262
263 my $count = scalar @{$parents};
264 my $noun = PL_N( 'parent', $count );
265
266 is( scalar @isa, $count, "$class has $count $noun" );
267
268 for ( my $i = 0; $i < @{$parents}; $i++ ) {
269 is( $isa[$i], $parents->[$i], "parent[$i] is $parents->[$i]" );
270 }
271}
272
ddd87d75 273sub has_rw_attr {
274 my $class = shift;
275 my $name = shift;
276
8d1ce1d7 277 my $articled = A($name);
5cab7e05 278 ok( $class->meta->has_attribute($name),
8d1ce1d7 279 "$class has $articled attribute" );
ddd87d75 280
281 my $attr = $class->meta->get_attribute($name);
282
8d1ce1d7 283 is( $attr->get_read_method, $name,
284 "$name attribute has a reader accessor - $name()" );
285 is( $attr->get_write_method, $name,
286 "$name attribute has a writer accessor - $name()" );
ddd87d75 287}
288
289sub has_ro_attr {
290 my $class = shift;
291 my $name = shift;
292
8d1ce1d7 293 my $articled = A($name);
5cab7e05 294 ok( $class->meta->has_attribute($name),
8d1ce1d7 295 "$class has $articled attribute" );
ddd87d75 296
297 my $attr = $class->meta->get_attribute($name);
298
8d1ce1d7 299 is( $attr->get_read_method, $name,
300 "$name attribute has a reader accessor - $name()" );
301 is( $attr->get_write_method, undef,
302 "$name attribute does not have a writer" );
ddd87d75 303}
304
305sub has_method {
306 my $class = shift;
307 my $name = shift;
308
8d1ce1d7 309 my $articled = A($name);
310 ok( $class->meta->has_method($name), "$class has $articled method" );
ddd87d75 311}
312
313sub has_overridden_method {
314 my $class = shift;
315 my $name = shift;
316
8d1ce1d7 317 my $articled = A($name);
318 ok( $class->meta->has_method($name), "$class has $articled method" );
ddd87d75 319
320 my $meth = $class->meta->get_method($name);
321 isa_ok( $meth, 'Moose::Meta::Method::Overridden' );
322}
323
538499df 324sub has_augmented_method {
325 my $class = shift;
326 my $name = shift;
327
328 my $articled = A($name);
329 ok( $class->meta->has_method($name), "$class has $articled method" );
330
331 my $meth = $class->meta->get_method($name);
332 isa_ok( $meth, 'Moose::Meta::Method::Augmented' );
333}
334
ddd87d75 335sub no_droppings {
336 my $class = shift;
337
338 ok( !$class->can('has'), "no Moose droppings in $class" );
ad648c43 339 ok( !$class->can('subtype'), "no Moose::Util::TypeConstraints droppings in $class" );
ddd87d75 340}
341
342sub is_immutable {
343 my $class = shift;
344
345 ok( $class->meta->is_immutable, "$class has been made immutable" );
346}
347
5cab7e05 348sub does_role {
349 my $class = shift;
350 my $role = shift;
351
352 ok( $class->meta->does_role($role), "$class does the $role role" );
353}
354
ddd87d75 355sub person01 {
356 my $person = Person->new(
357 first_name => 'Bilbo',
358 last_name => 'Baggins',
359 );
360
8d1ce1d7 361 is( $person->full_name, 'Bilbo Baggins',
362 'full_name() is correctly implemented' );
f7da468c 363
364 $person = Person->new( [ qw( Lisa Smith ) ] );
365 is( $person->first_name, 'Lisa', 'set first_name from two-arg arrayref' );
366 is( $person->last_name, 'Smith', 'set last_name from two-arg arrayref' );
367
368 eval { Person->new( sub { 'foo' } ) };
369 like( $@, qr/\QSingle parameters to new() must be a HASH ref/,
370 'Person constructor still rejects bad parameters' );
ddd87d75 371}
372
373sub employee01 {
374 my $employee = Employee->new(
375 first_name => 'Amanda',
376 last_name => 'Palmer',
8d1ce1d7 377 title => 'Singer',
ddd87d75 378 );
379
54b470f5 380 my $called = 0;
381 my $orig_super = \&Employee::super;
382 no warnings 'redefine';
383 local *Employee::super = sub { $called++; goto &$orig_super };
384
f555d2ec 385 is( $employee->full_name, 'Amanda Palmer (Singer)',
386 'full_name() is properly overriden in Employee' );
54b470f5 387 ok( $called, 'Employee->full_name calls super()' );
ddd87d75 388}
389
5cab7e05 390sub person02 {
391 my $person = Person->new(
392 first_name => 'Bilbo',
393 last_name => 'Baggins',
394 balance => 0,
395 );
396
8d1ce1d7 397 is( $person->as_string, 'Bilbo Baggins',
398 'as_string() is correctly implemented' );
5cab7e05 399
400 account_tests($person);
401}
402
403sub employee02 {
404 my $employee = Employee->new(
405 first_name => 'Amanda',
406 last_name => 'Palmer',
8d1ce1d7 407 title => 'Singer',
5cab7e05 408 balance => 0,
409 );
410
8d1ce1d7 411 is( $employee->as_string, 'Amanda Palmer (Singer)',
412 'as_string() uses overridden full_name method in Employee' );
5cab7e05 413
414 account_tests($employee);
415}
416
8d1ce1d7 417sub person03 {
418 my $person = Person->new(
419 first_name => 'Bilbo',
420 last_name => 'Baggins',
421 );
422
423 is( $person->full_name, 'Bilbo Baggins',
424 'full_name() is correctly implemented for a Person without a title' );
425 ok( !$person->has_title,
3647da1b 426 'Person has_title predicate is working correctly (returns false)' );
8d1ce1d7 427
428 $person->title('Ringbearer');
3647da1b 429 ok( $person->has_title, 'Person has_title predicate is working correctly (returns true)' );
430
3647da1b 431 my $called = 0;
54b470f5 432 my $orig_pred = \&Person::has_title;
433 no warnings 'redefine';
434 local *Person::has_title = sub { $called++; goto &$orig_pred };
435
8d1ce1d7 436 is( $person->full_name, 'Bilbo Baggins (Ringbearer)',
437 'full_name() is correctly implemented for a Person with a title' );
3647da1b 438 ok( $called, 'full_name in person uses the predicate for the title attribute' );
8d1ce1d7 439
440 $person->clear_title;
441 ok( !$person->has_title, 'Person clear_title method cleared the title' );
442
443 account_tests( $person, 100 );
444}
445
446sub employee03 {
447 my $employee = Employee->new(
448 first_name => 'Jimmy',
449 last_name => 'Foo',
450 salary_level => 3,
451 salary => 42,
452 );
453
454 is( $employee->salary, 30000,
455 'salary is calculated from salary_level, and salary passed to constructor is ignored' );
456}
457
66b226e5 458sub person06 {
459 my $person = Person->new(
460 first_name => 'Bilbo',
461 last_name => 'Baggins',
462 );
463
464 isa_ok( $person->account, 'BankAccount' );
465 is( $person->account->owner, $person,
466 'owner of bank account is person that created account' );
467
468 $person->deposit(10);
469 is_deeply( $person->account->history, [ 100, 10 ],
470 'deposit was recorded in account history' );
471
472 $person->withdraw(15);
473 is_deeply( $person->account->history, [ 100, 10, -15 ],
474 'withdrawal was recorded in account history' );
475}
476
5cab7e05 477sub account_tests {
478 local $Test::Builder::Level = $Test::Builder::Level + 1;
479
480 my $person = shift;
8d1ce1d7 481 my $base_amount = shift || 0;
5cab7e05 482
483 $person->deposit(50);
8d1ce1d7 484 eval { $person->withdraw( 75 + $base_amount ) };
485 like( $@, qr/\QBalance cannot be negative/,
486 'cannot withdraw more than is in our balance' );
5cab7e05 487
8d1ce1d7 488 $person->withdraw( 23 );
5cab7e05 489
8d1ce1d7 490 is( $person->balance, 27 + $base_amount,
491 'balance is 27 (+ starting balance) after deposit of 50 and withdrawal of 23' );
5cab7e05 492}
ddd87d75 493
4941;