support for hashref in set_attribute + add_to_attribute
[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 name="cow" 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( 'class' => 'foo' ) },
54   $expect,
55   'set attribute on existing attribute'
56 );
57
58 ($expect = $tmpl) =~ s/name="cow" class="main"/name="bar" class="foo"/;
59
60 is(
61   run_for { $_->set_attribute({ 'class' => 'foo', 'name' => 'bar'}) },
62   $expect,
63   'set attributes using hashref form'
64 );
65
66 ($expect = $tmpl) =~ s/class="main"/class="main" foo="bar"/;
67
68 is(
69   run_for { $_->set_attribute( 'foo' => '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( 'class' => 'foo' ) },
78   $expect,
79   'add attribute on existing attribute'
80 );
81
82 ($expect = $tmpl) =~ s/class="main"/class="main" foo="bar"/;
83
84 is(
85   run_for { $_->add_to_attribute( 'foo' => 'bar' ) },
86   $expect,
87   'add attribute on non existing attribute'
88 );
89
90 ($expect = $tmpl) =~ s/ class="main"//;
91
92 is(
93   run_for { $_->remove_attribute({ name => 'class' }) },
94   $expect,
95   'remove attribute on existing attribute'
96 );
97
98 is(
99   run_for { $_->remove_attribute({ name => 'foo' }) },
100   $tmpl,
101   'remove attribute on non existing attribute'
102 );
103
104 ($expect = $tmpl) =~ s/ class="main"//;
105
106 is(
107   run_for {
108       $_->transform_attribute({
109           name => 'class',
110           code => sub {
111               my $a = shift;
112               return if $a eq 'main';
113               return $a;
114           },
115       })
116   },
117   $expect,
118   'transform_attribute deletes the attr if code returns undef',
119   );
120
121 ($expect = $tmpl) =~ s/ class="main"/ class="moan"/;
122
123 is(
124   run_for {
125       $_->transform_attribute({
126           name => 'class',
127           code => sub {
128               ( my $b = shift ) =~ s/main/moan/;
129               $b
130           },
131       })
132   },
133   $expect,
134   'transform_attribute transforms something',
135   );
136
137 ($expect = $tmpl) =~ s/ class="main"/ class="main" noggin="zonk"/;
138
139 is(
140   run_for {
141       $_->transform_attribute({
142           name => 'noggin',
143           code => sub { 'zonk' },
144       })
145   },
146   $expect,
147   'transform_attribute adds attribute if not there before',
148   );
149
150 is(
151   run_for {
152       $_->transform_attribute({
153           name => 'noggin',
154           code => sub { },
155       })
156   },
157   $tmpl,
158   'transform_attribute on nonexistent att does not add it if code returns undef',
159   );
160
161
162 ($expect = $tmpl) =~ s/(?=<div)/O HAI/;
163
164 my $ohai = [ { type => 'TEXT', raw => 'O HAI' } ];
165
166 is(
167   run_for { $_->add_before($ohai) },
168   $expect,
169   'add_before ok'
170 );
171
172 ($expect = $tmpl) =~ s/(?<=<\/div>)/O HAI/;
173
174 is(
175   run_for { $_->add_after($ohai) },
176   $expect,
177   'add_after ok'
178 );
179
180 ($expect = $tmpl) =~ s/(?<=class="main">)/O HAI/;
181
182 is(
183   run_for { $_->prepend_content($ohai) },
184   $expect,
185   'prepend_content ok'
186 );
187
188 ($expect = $tmpl) =~ s/<hr \/>/<hr>O HAI<\/hr>/;
189
190 is(
191   (run_for { $_->prepend_content($ohai) } 'hr'),
192   $expect,
193   'prepend_content ok with in place close'
194 );
195
196 is(
197   run_for { $_->replace($ohai) },
198 '<body>
199   O HAI
200 </body>
201 ',
202   'replace ok'
203 );
204
205 @ev = ();
206
207 is(
208   run_for { $_->collect({ into => \@ev }) },
209   '<body>
210   
211 </body>
212 ',
213   'collect removes without passthrough'
214 );
215
216 is(
217   HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
218   '<div name="cow" class="main">
219     <span class="hilight name">Bob</span>
220     <span class="career">Builder</span>
221     <hr />
222   </div>',
223   'collect collected right events'
224 );
225
226 @ev = ();
227
228 is(
229   run_for { $_->collect({ into => \@ev, content => 1 }) },
230   '<body>
231   <div name="cow" class="main"></div>
232 </body>
233 ',
234   'collect w/content removes correctly'
235 );
236
237 is(
238   HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
239   '
240     <span class="hilight name">Bob</span>
241     <span class="career">Builder</span>
242     <hr />
243   ',
244   'collect w/content collects correctly'
245 );
246
247 is(
248   run_for { $_->replace($ohai, { content => 1 }) },
249   '<body>
250   <div name="cow" class="main">O HAI</div>
251 </body>
252 ',
253   'replace w/content'
254 );
255
256 ($expect = $tmpl) =~ s/(?=<\/div>)/O HAI/;
257
258 is(
259   run_for { $_->append_content($ohai) },
260   $expect,
261   'append content ok'
262 );
263
264 my $r_content = sub { my $r = shift; sub { $_->replace($r, { content => 1 }) } };
265
266 is(
267   run_for {
268     $_->repeat(
269       [
270         sub {
271           filter
272             filter($_ => '.name' => $r_content->('mst'))
273             => '.career' => $r_content->('Chainsaw Wielder')
274         },
275         sub {
276           filter
277             filter($_ => '.name' => $r_content->('mdk'))
278             => '.career' => $r_content->('Adminion')
279         },
280       ]
281     )
282   },
283   q{<body>
284   <div name="cow" class="main">
285     <span class="hilight name">mst</span>
286     <span class="career">Chainsaw Wielder</span>
287     <hr />
288   </div><div name="cow" class="main">
289     <span class="hilight name">mdk</span>
290     <span class="career">Adminion</span>
291     <hr />
292   </div>
293 </body>
294 },
295   'repeat ok'
296 );
297
298 is(
299   run_for {
300     $_->repeat_content(
301       [
302         sub {
303           filter
304             filter($_ => '.name' => $r_content->('mst'))
305             => '.career' => $r_content->('Chainsaw Wielder')
306         },
307         sub {
308           filter
309             filter($_ => '.name' => $r_content->('mdk'))
310             => '.career' => $r_content->('Adminion')
311         },
312       ]
313     )
314   },
315   q{<body>
316   <div name="cow" class="main">
317     <span class="hilight name">mst</span>
318     <span class="career">Chainsaw Wielder</span>
319     <hr />
320   
321     <span class="hilight name">mdk</span>
322     <span class="career">Adminion</span>
323     <hr />
324   </div>
325 </body>
326 },
327   'repeat_content ok'
328 );
329
330 is(
331   run_for {
332     my @between;
333     $_->repeat_content(
334       [
335         sub {
336           HTML::Zoom::ArrayStream->new({ array => [
337             (filter
338               filter($_ => '.name' => $r_content->('mst'))
339               => '.career' => $r_content->('Chainsaw Wielder')),
340             HTML::Zoom::ArrayStream->new({ array => \@between })
341           ] })->flatten
342         },
343         sub {
344           filter
345             filter($_ => '.name' => $r_content->('mdk'))
346             => '.career' => $r_content->('Adminion')
347         },
348       ],
349       { filter => sub {
350           filter $_[0] => 'hr' => sub { $_->collect({ into => \@between }) }
351         }
352       }
353     )
354   },
355   q{<body>
356   <div name="cow" class="main">
357     <span class="hilight name">mst</span>
358     <span class="career">Chainsaw Wielder</span>
359     <hr />
360     <span class="hilight name">mdk</span>
361     <span class="career">Adminion</span>
362     
363   </div>
364 </body>
365 },
366   'repeat_content with filter ok'
367 );
368
369 is(
370   run_for {
371     my @between;
372     $_->repeat_content(
373       [
374         sub {
375           filter
376             filter($_ => '.name' => $r_content->('mst'))
377             => '.career' => $r_content->('Chainsaw Wielder')
378         },
379         sub {
380           filter
381             filter($_ => '.name' => $r_content->('mdk'))
382             => '.career' => $r_content->('Adminion')
383         },
384       ],
385       { repeat_between => 'hr' }
386     )
387   },
388   q{<body>
389   <div name="cow" class="main">
390     <span class="hilight name">mst</span>
391     <span class="career">Chainsaw Wielder</span>
392     <hr />
393     <span class="hilight name">mdk</span>
394     <span class="career">Adminion</span>
395     
396   </div>
397 </body>
398 },
399   'repeat_content using repeat_between ok'
400 );
401
402 done_testing;