More tests for setup_components, some for locate_components
[catagits/Catalyst-Runtime.git] / t / aggregate / unit_core_component_loading.t
1 # 3 initial tests, and 6 per component in the loop below
2 # (do not forget to update the number of components in test 3 as well)
3 # 5 extra tests for the loading options
4 # One test for components in inner packages
5 use Test::More tests => 3 + 6 * 24 + 9 + 1;
6
7 use strict;
8 use warnings;
9
10 use File::Spec;
11 use File::Path;
12
13 my $libdir = 'test_trash';
14 local @INC = @INC;
15 unshift(@INC, $libdir);
16
17 my $appclass = 'TestComponents';
18 my @components = (
19     { type => 'Controller', prefix => 'C', name => 'Bar' },
20     { type => 'Controller', prefix => 'C', name => 'Foo::Bar' },
21     { type => 'Controller', prefix => 'C', name => 'Foo::Foo::Bar' },
22     { type => 'Controller', prefix => 'C', name => 'Foo::Foo::Foo::Bar' },
23     { type => 'Controller', prefix => 'Controller', name => 'Bar::Bar::Bar::Foo' },
24     { type => 'Controller', prefix => 'Controller', name => 'Bar::Bar::Foo' },
25     { type => 'Controller', prefix => 'Controller', name => 'Bar::Foo' },
26     { type => 'Controller', prefix => 'Controller', name => 'Foo' },
27     { type => 'Model', prefix => 'M', name => 'Bar' },
28     { type => 'Model', prefix => 'M', name => 'Foo::Bar' },
29     { type => 'Model', prefix => 'M', name => 'Foo::Foo::Bar' },
30     { type => 'Model', prefix => 'M', name => 'Foo::Foo::Foo::Bar' },
31     { type => 'Model', prefix => 'Model', name => 'Bar::Bar::Bar::Foo' },
32     { type => 'Model', prefix => 'Model', name => 'Bar::Bar::Foo' },
33     { type => 'Model', prefix => 'Model', name => 'Bar::Foo' },
34     { type => 'Model', prefix => 'Model', name => 'Foo' },
35     { type => 'View', prefix => 'V', name => 'Bar' },
36     { type => 'View', prefix => 'V', name => 'Foo::Bar' },
37     { type => 'View', prefix => 'V', name => 'Foo::Foo::Bar' },
38     { type => 'View', prefix => 'V', name => 'Foo::Foo::Foo::Bar' },
39     { type => 'View', prefix => 'View', name => 'Bar::Bar::Bar::Foo' },
40     { type => 'View', prefix => 'View', name => 'Bar::Bar::Foo' },
41     { type => 'View', prefix => 'View', name => 'Bar::Foo' },
42     { type => 'View', prefix => 'View', name => 'Foo' },
43 );
44
45 sub write_component_file {
46   my ($dir_list, $module_name, $content) = @_;
47
48   my $dir  = File::Spec->catdir(@$dir_list);
49   my $file = File::Spec->catfile($dir, $module_name . '.pm');
50
51   mkpath(join(q{/}, @$dir_list) );
52   open(my $fh, '>', $file) or die "Could not open file $file for writing: $!";
53   print $fh $content;
54   close $fh;
55 }
56
57 sub make_component_file {
58     my ($libdir, $appclass, $type, $prefix, $name) = @_;
59
60     my $compbase = "Catalyst::${type}";
61     my $fullname = "${appclass}::${prefix}::${name}";
62     my @namedirs = split(/::/, $name);
63     my $name_final = pop(@namedirs);
64     my @dir_list = ($libdir, $appclass, $prefix, @namedirs);
65
66     write_component_file(\@dir_list, $name_final, <<EOF);
67 package $fullname;
68 use MRO::Compat;
69 use base '$compbase';
70 sub COMPONENT {
71     my \$self = shift->next::method(\@_);
72     no strict 'refs';
73     *{\__PACKAGE__ . "::whoami"} = sub { return \__PACKAGE__; };
74     \$self;
75 }
76 1;
77
78 EOF
79 }
80
81 foreach my $component (@components) {
82     make_component_file(
83         $libdir,
84         $appclass,
85         $component->{type},
86         $component->{prefix},
87         $component->{name},
88     );
89 }
90
91 my $shut_up_deprecated_warnings = q{
92     __PACKAGE__->log(Catalyst::Log->new('fatal'));
93 };
94
95 eval "package $appclass; use Catalyst; $shut_up_deprecated_warnings __PACKAGE__->setup";
96
97 is_deeply(
98     [ sort $appclass->locate_components ],
99     [ map { $appclass . '::' . $_->{prefix} . '::' . $_->{name} } @components ],    'locate_components finds the components correctly'
100 );
101
102 can_ok( $appclass, 'components');
103
104 my $complist = $appclass->components;
105
106 # the +1 below is for the app class itself
107 is(scalar keys %$complist, 24+1, "Correct number of components loaded");
108
109 foreach (keys %$complist) {
110
111     # Skip the component which happens to be the app itself
112     next if $_ eq $appclass;
113
114     my $instance = $appclass->component($_);
115     isa_ok($instance, $_);
116     can_ok($instance, 'whoami');
117     is($instance->whoami, $_);
118
119     if($_ =~ /^${appclass}::(?:V|View)::(.*)/) {
120         my $moniker = $1;
121         isa_ok($instance, 'Catalyst::View');
122         can_ok($appclass->view($moniker), 'whoami');
123         is($appclass->view($moniker)->whoami, $_);
124     }
125     elsif($_ =~ /^${appclass}::(?:M|Model)::(.*)/) {
126         my $moniker = $1;
127         isa_ok($instance, 'Catalyst::Model');
128         can_ok($appclass->model($moniker), 'whoami');
129         is($appclass->model($moniker)->whoami, $_);
130     }
131     elsif($_ =~ /^${appclass}::(?:C|Controller)::(.*)/) {
132         my $moniker = $1;
133         isa_ok($instance, 'Catalyst::Controller');
134         can_ok($appclass->controller($moniker), 'whoami');
135         is($appclass->controller($moniker)->whoami, $_);
136     }
137     else {
138         die "Something is wrong with this test, this should"
139             . " have been unreachable";
140     }
141 }
142
143 rmtree($libdir);
144
145 # test extra component loading options
146
147 $appclass = 'ExtraOptions';
148 push @components, { type => 'View', prefix => 'Extra', name => 'Foo' };
149
150 foreach my $component (@components) {
151     make_component_file(
152         $libdir,
153         $appclass,
154         $component->{type},
155         $component->{prefix},
156         $component->{name},
157     );
158 }
159
160 eval qq(
161 package $appclass;
162 use Catalyst;
163 $shut_up_deprecated_warnings
164 __PACKAGE__->config->{ setup_components } = {
165     search_extra => [ '::Extra' ],
166     except       => [ "${appclass}::Controller::Foo" ]
167 };
168 __PACKAGE__->setup;
169 );
170
171 {
172     my $config = {
173         search_extra => [ '::Extra' ],
174         except       => [ "${appclass}::Controller::Foo" ]
175     };
176     my @components_located = $appclass->locate_components($config);
177     my @components_expected;
178     for (@components) {
179         my $name = $appclass . '::' . $_->{prefix} . '::' . $_->{name};
180         push @components_expected, $name if $name ne "${appclass}::Controller::Foo";
181     }
182     is_deeply(
183         [ sort @components_located ],
184         [ sort @components_expected ],
185         'locate_components finds the components correctly'
186     );
187 }
188
189 can_ok( $appclass, 'components');
190
191 $complist = $appclass->components;
192
193 is(scalar keys %$complist, 24+1, "Correct number of components loaded");
194
195 ok( !exists $complist->{ "${appclass}::Controller::Foo" }, 'Controller::Foo was skipped' );
196 ok( exists $complist->{ "${appclass}::Extra::Foo" }, 'Extra::Foo was loaded' );
197
198 rmtree($libdir);
199
200 $appclass = "ComponentOnce";
201
202 write_component_file([$libdir, $appclass, 'Model'], 'TopLevel', <<EOF);
203 package ${appclass}::Model::TopLevel;
204 use base 'Catalyst::Model';
205 sub COMPONENT {
206
207     my \$self = shift->next::method(\@_);
208     no strict 'refs';
209     *{\__PACKAGE__ . "::whoami"} = sub { return \__PACKAGE__; };
210     *${appclass}::Model::TopLevel::GENERATED::ACCEPT_CONTEXT = sub {
211         return bless {}, 'FooBarBazQuux';
212     };
213     \$self;
214 }
215
216 package ${appclass}::Model::TopLevel::Nested;
217
218 sub COMPONENT { die "COMPONENT called in the wrong order!"; }
219
220 1;
221
222 EOF
223
224 write_component_file([$libdir, $appclass, 'Model', 'TopLevel'], 'Nested', <<EOF);
225 package ${appclass}::Model::TopLevel::Nested;
226 use base 'Catalyst::Model';
227
228 my \$called=0;
229 no warnings 'redefine';
230 sub COMPONENT { \$called++;return shift->next::method(\@_); }
231 sub called { return \$called };
232 1;
233
234 EOF
235
236 eval "package $appclass; use Catalyst; __PACKAGE__->setup";
237
238 is($@, '', "Didn't load component twice");
239 is($appclass->model('TopLevel::Nested')->called,1, 'COMPONENT called once');
240
241 ok($appclass->model('TopLevel::Generated'), 'Have generated model');
242 is(ref($appclass->model('TopLevel::Generated')), 'FooBarBazQuux',
243     'ACCEPT_CONTEXT in generated inner package fired as expected');
244
245 $appclass = "InnerComponent";
246
247 {
248   package InnerComponent::Controller::Test;
249   use base 'Catalyst::Controller';
250 }
251
252 $INC{'InnerComponent/Controller/Test.pm'} = 1;
253
254 eval "package $appclass; use Catalyst; __PACKAGE__->setup";
255
256 isa_ok($appclass->controller('Test'), 'Catalyst::Controller');
257
258 rmtree($libdir);