some simple shorthand helpers.
[catagits/HTML-Zoom.git] / t / actions.t
1 use strictures 1;
2 use Test::More;
3
4 use HTML::Zoom::Parser::BuiltIn;
5 use HTML::Zoom::Producer::BuiltIn;
6 use HTML::Zoom::SelectorParser;
7 use HTML::Zoom::FilterBuilder;
8 use HTML::Zoom::FilterStream;
9
10 my $tmpl = <<END;
11 <body>
12   <div class="main">
13     <span class="hilight name">Bob</span>
14     <span class="career">Builder</span>
15     <hr />
16   </div>
17 </body>
18 END
19
20 sub src_stream { HTML::Zoom::Parser::BuiltIn->new->html_to_stream($tmpl); }
21
22 sub html_sink { HTML::Zoom::Producer::BuiltIn->new->html_from_stream($_[0]) }
23
24 my $fb = HTML::Zoom::FilterBuilder->new;
25
26 my $sp = HTML::Zoom::SelectorParser->new;
27
28 sub filter {
29   my ($stream, $sel, $cb) = @_;
30   return HTML::Zoom::FilterStream->new({
31     stream => $stream,
32     match => $sp->parse_selector($sel),
33     filter => do { local $_ = $fb; $cb->($fb) }
34   });
35 }
36
37 sub run_for (&;$) {
38   my $cb = shift;
39   (html_sink
40     (filter
41       src_stream,
42       (shift or '.main'),
43       $cb
44     )
45   )
46 }
47
48 my ($expect, @ev);
49
50 ($expect = $tmpl) =~ s/class="main"/class="foo"/;
51
52 is(
53   run_for { $_->set_attribute({ name => 'class', value => 'foo' }) },
54   $expect,
55   'set attribute on existing attribute'
56 );
57
58 ($expect = $tmpl) =~ s/class="main"/class="foo"/;
59
60 is(
61   run_for { $_->set_attr({ name => 'class', value => 'foo' }) },
62   $expect,
63   'set attribute on existing attribute (shorthand)'
64 );
65
66 ($expect = $tmpl) =~ s/class="main"/class="main" foo="bar"/;
67
68 is(
69   run_for { $_->set_attribute({ name => 'foo', value => 'bar' }) },
70   $expect,
71   'set attribute on non existing attribute'
72 );
73
74 ($expect = $tmpl) =~ s/class="main"/class="main foo"/;
75
76 is(
77   run_for { $_->add_to_attribute({ name => 'class', value => 'foo' }) },
78   $expect,
79   'add attribute on existing attribute'
80 );
81
82 ($expect = $tmpl) =~ s/class="main"/class="main foo"/;
83
84 is(
85   run_for { $_->add_class('foo') },
86   $expect,
87   'add attribute on existing attribute (shorthand)'
88 );
89
90 ($expect = $tmpl) =~ s/class="main"/class="main" id="foo"/;
91
92 is(
93   run_for { $_->set_id('foo') },
94   $expect,
95   'set_id (shorthand)'
96 );
97
98 ($expect = $tmpl) =~ s/class="main"/class="main" foo="bar"/;
99
100 is(
101   run_for { $_->add_to_attribute({ name => 'foo', value => 'bar' }) },
102   $expect,
103   'add attribute on non existing attribute'
104 );
105
106 ($expect = $tmpl) =~ s/ class="main"//;
107
108 is(
109   run_for { $_->remove_attribute({ name => 'class' }) },
110   $expect,
111   'remove attribute on existing attribute'
112 );
113
114 is(
115   run_for { $_->remove_attribute({ name => 'foo' }) },
116   $tmpl,
117   'remove attribute on non existing attribute'
118 );
119
120 ($expect = $tmpl) =~ s/ class="main"//;
121
122 is(
123   run_for {
124       $_->transform_attribute({
125           name => 'class',
126           code => sub {
127               my $a = shift;
128               return if $a eq 'main';
129               return $a;
130           },
131       })
132   },
133   $expect,
134   'transform_attribute deletes the attr if code returns undef',
135   );
136
137 ($expect = $tmpl) =~ s/ class="main"/ class="moan"/;
138
139 is(
140   run_for {
141       $_->transform_attribute({
142           name => 'class',
143           code => sub {
144               ( my $b = shift ) =~ s/main/moan/;
145               $b
146           },
147       })
148   },
149   $expect,
150   'transform_attribute transforms something',
151   );
152
153 ($expect = $tmpl) =~ s/ class="main"/ class="main" noggin="zonk"/;
154
155 is(
156   run_for {
157       $_->transform_attribute({
158           name => 'noggin',
159           code => sub { 'zonk' },
160       })
161   },
162   $expect,
163   'transform_attribute adds attribute if not there before',
164   );
165
166 is(
167   run_for {
168       $_->transform_attribute({
169           name => 'noggin',
170           code => sub { },
171       })
172   },
173   $tmpl,
174   'transform_attribute on nonexistent att does not add it if code returns undef',
175   );
176
177
178 ($expect = $tmpl) =~ s/(?=<div)/O HAI/;
179
180 my $ohai = [ { type => 'TEXT', raw => 'O HAI' } ];
181
182 is(
183   run_for { $_->add_before($ohai) },
184   $expect,
185   'add_before ok'
186 );
187
188 ($expect = $tmpl) =~ s/(?<=<\/div>)/O HAI/;
189
190 is(
191   run_for { $_->add_after($ohai) },
192   $expect,
193   'add_after ok'
194 );
195
196 ($expect = $tmpl) =~ s/(?<=class="main">)/O HAI/;
197
198 is(
199   run_for { $_->prepend_content($ohai) },
200   $expect,
201   'prepend_content ok'
202 );
203
204 ($expect = $tmpl) =~ s/<hr \/>/<hr>O HAI<\/hr>/;
205
206 is(
207   (run_for { $_->prepend_content($ohai) } 'hr'),
208   $expect,
209   'prepend_content ok with in place close'
210 );
211
212 is(
213   run_for { $_->replace($ohai) },
214 '<body>
215   O HAI
216 </body>
217 ',
218   'replace ok'
219 );
220
221 @ev = ();
222
223 is(
224   run_for { $_->collect({ into => \@ev }) },
225   '<body>
226   
227 </body>
228 ',
229   'collect removes without passthrough'
230 );
231
232 is(
233   HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
234   '<div class="main">
235     <span class="hilight name">Bob</span>
236     <span class="career">Builder</span>
237     <hr />
238   </div>',
239   'collect collected right events'
240 );
241
242 @ev = ();
243
244 is(
245   run_for { $_->collect({ into => \@ev, content => 1 }) },
246   '<body>
247   <div class="main"></div>
248 </body>
249 ',
250   'collect w/content removes correctly'
251 );
252
253 is(
254   HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
255   '
256     <span class="hilight name">Bob</span>
257     <span class="career">Builder</span>
258     <hr />
259   ',
260   'collect w/content collects correctly'
261 );
262
263 is(
264   run_for { $_->replace($ohai, { content => 1 }) },
265   '<body>
266   <div class="main">O HAI</div>
267 </body>
268 ',
269   'replace w/content'
270 );
271
272 ($expect = $tmpl) =~ s/(?=<\/div>)/O HAI/;
273
274 is(
275   run_for { $_->append_content($ohai) },
276   $expect,
277   'append content ok'
278 );
279
280 my $r_content = sub { my $r = shift; sub { $_->replace($r, { content => 1 }) } };
281
282 is(
283   run_for {
284     $_->repeat(
285       [
286         sub {
287           filter
288             filter($_ => '.name' => $r_content->('mst'))
289             => '.career' => $r_content->('Chainsaw Wielder')
290         },
291         sub {
292           filter
293             filter($_ => '.name' => $r_content->('mdk'))
294             => '.career' => $r_content->('Adminion')
295         },
296       ]
297     )
298   },
299   q{<body>
300   <div class="main">
301     <span class="hilight name">mst</span>
302     <span class="career">Chainsaw Wielder</span>
303     <hr />
304   </div><div class="main">
305     <span class="hilight name">mdk</span>
306     <span class="career">Adminion</span>
307     <hr />
308   </div>
309 </body>
310 },
311   'repeat ok'
312 );
313
314 is(
315   run_for {
316     $_->repeat_content(
317       [
318         sub {
319           filter
320             filter($_ => '.name' => $r_content->('mst'))
321             => '.career' => $r_content->('Chainsaw Wielder')
322         },
323         sub {
324           filter
325             filter($_ => '.name' => $r_content->('mdk'))
326             => '.career' => $r_content->('Adminion')
327         },
328       ]
329     )
330   },
331   q{<body>
332   <div class="main">
333     <span class="hilight name">mst</span>
334     <span class="career">Chainsaw Wielder</span>
335     <hr />
336   
337     <span class="hilight name">mdk</span>
338     <span class="career">Adminion</span>
339     <hr />
340   </div>
341 </body>
342 },
343   'repeat_content ok'
344 );
345
346 is(
347   run_for {
348     my @between;
349     $_->repeat_content(
350       [
351         sub {
352           HTML::Zoom::ArrayStream->new({ array => [
353             (filter
354               filter($_ => '.name' => $r_content->('mst'))
355               => '.career' => $r_content->('Chainsaw Wielder')),
356             HTML::Zoom::ArrayStream->new({ array => \@between })
357           ] })->flatten
358         },
359         sub {
360           filter
361             filter($_ => '.name' => $r_content->('mdk'))
362             => '.career' => $r_content->('Adminion')
363         },
364       ],
365       { filter => sub {
366           filter $_[0] => 'hr' => sub { $_->collect({ into => \@between }) }
367         }
368       }
369     )
370   },
371   q{<body>
372   <div class="main">
373     <span class="hilight name">mst</span>
374     <span class="career">Chainsaw Wielder</span>
375     <hr />
376     <span class="hilight name">mdk</span>
377     <span class="career">Adminion</span>
378     
379   </div>
380 </body>
381 },
382   'repeat_content with filter ok'
383 );
384
385 is(
386   run_for {
387     my @between;
388     $_->repeat_content(
389       [
390         sub {
391           filter
392             filter($_ => '.name' => $r_content->('mst'))
393             => '.career' => $r_content->('Chainsaw Wielder')
394         },
395         sub {
396           filter
397             filter($_ => '.name' => $r_content->('mdk'))
398             => '.career' => $r_content->('Adminion')
399         },
400       ],
401       { repeat_between => 'hr' }
402     )
403   },
404   q{<body>
405   <div class="main">
406     <span class="hilight name">mst</span>
407     <span class="career">Chainsaw Wielder</span>
408     <hr />
409     <span class="hilight name">mdk</span>
410     <span class="career">Adminion</span>
411     
412   </div>
413 </body>
414 },
415   'repeat_content using repeat_between ok'
416 );
417
418 done_testing;