ignore if attr doesn't exist.
[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( 'class' => '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( '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"/;
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( 'foo' => '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"/class=""/;
121
122 is(
123   run_for { $_->remove_from_attribute({ class => 'main' }) },
124   $expect,
125   'remove name from attribute'
126 );
127
128 is(
129   run_for { $_->remove_from_attribute({ madeup => 'main' }) },
130   $tmpl,
131   'remove name from non existing attribute (ignored)'
132 );
133
134 ($expect = $tmpl) =~ s/class="main"/class=""/;
135
136 is(
137   run_for { $_->remove_class( 'main' ) },
138   $expect,
139   'remove_class'
140 );
141
142 ($expect = $tmpl) =~ s/ class="main"//;
143
144 is(
145   run_for {
146       $_->transform_attribute({
147           name => 'class',
148           code => sub {
149               my $a = shift;
150               return if $a eq 'main';
151               return $a;
152           },
153       })
154   },
155   $expect,
156   'transform_attribute deletes the attr if code returns undef',
157   );
158
159 ($expect = $tmpl) =~ s/ class="main"/ class="moan"/;
160
161 is(
162   run_for {
163       $_->transform_attribute({
164           name => 'class',
165           code => sub {
166               ( my $b = shift ) =~ s/main/moan/;
167               $b
168           },
169       })
170   },
171   $expect,
172   'transform_attribute transforms something',
173   );
174
175 ($expect = $tmpl) =~ s/ class="main"/ class="main" noggin="zonk"/;
176
177 is(
178   run_for {
179       $_->transform_attribute({
180           name => 'noggin',
181           code => sub { 'zonk' },
182       })
183   },
184   $expect,
185   'transform_attribute adds attribute if not there before',
186   );
187
188 is(
189   run_for {
190       $_->transform_attribute({
191           name => 'noggin',
192           code => sub { },
193       })
194   },
195   $tmpl,
196   'transform_attribute on nonexistent att does not add it if code returns undef',
197   );
198
199
200 ($expect = $tmpl) =~ s/(?=<div)/O HAI/;
201
202 my $ohai = [ { type => 'TEXT', raw => 'O HAI' } ];
203
204 is(
205   run_for { $_->add_before($ohai) },
206   $expect,
207   'add_before ok'
208 );
209
210 ($expect = $tmpl) =~ s/(?<=<\/div>)/O HAI/;
211
212 is(
213   run_for { $_->add_after($ohai) },
214   $expect,
215   'add_after ok'
216 );
217
218 ($expect = $tmpl) =~ s/(?<=class="main">)/O HAI/;
219
220 is(
221   run_for { $_->prepend_content($ohai) },
222   $expect,
223   'prepend_content ok'
224 );
225
226 ($expect = $tmpl) =~ s/<hr \/>/<hr>O HAI<\/hr>/;
227
228 is(
229   (run_for { $_->prepend_content($ohai) } 'hr'),
230   $expect,
231   'prepend_content ok with in place close'
232 );
233
234 is(
235   run_for { $_->replace($ohai) },
236 '<body>
237   O HAI
238 </body>
239 ',
240   'replace ok'
241 );
242
243 @ev = ();
244
245 is(
246   run_for { $_->collect({ into => \@ev }) },
247   '<body>
248   
249 </body>
250 ',
251   'collect removes without passthrough'
252 );
253
254 is(
255   HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
256   '<div class="main">
257     <span class="hilight name">Bob</span>
258     <span class="career">Builder</span>
259     <hr />
260   </div>',
261   'collect collected right events'
262 );
263
264 @ev = ();
265
266 is(
267   run_for { $_->collect({ into => \@ev, content => 1 }) },
268   '<body>
269   <div class="main"></div>
270 </body>
271 ',
272   'collect w/content removes correctly'
273 );
274
275 is(
276   HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
277   '
278     <span class="hilight name">Bob</span>
279     <span class="career">Builder</span>
280     <hr />
281   ',
282   'collect w/content collects correctly'
283 );
284
285 is(
286   run_for { $_->replace($ohai, { content => 1 }) },
287   '<body>
288   <div class="main">O HAI</div>
289 </body>
290 ',
291   'replace w/content'
292 );
293
294 ($expect = $tmpl) =~ s/(?=<\/div>)/O HAI/;
295
296 is(
297   run_for { $_->append_content($ohai) },
298   $expect,
299   'append content ok'
300 );
301
302 my $r_content = sub { my $r = shift; sub { $_->replace($r, { content => 1 }) } };
303
304 is(
305   run_for {
306     $_->repeat(
307       [
308         sub {
309           filter
310             filter($_ => '.name' => $r_content->('mst'))
311             => '.career' => $r_content->('Chainsaw Wielder')
312         },
313         sub {
314           filter
315             filter($_ => '.name' => $r_content->('mdk'))
316             => '.career' => $r_content->('Adminion')
317         },
318       ]
319     )
320   },
321   q{<body>
322   <div class="main">
323     <span class="hilight name">mst</span>
324     <span class="career">Chainsaw Wielder</span>
325     <hr />
326   </div><div class="main">
327     <span class="hilight name">mdk</span>
328     <span class="career">Adminion</span>
329     <hr />
330   </div>
331 </body>
332 },
333   'repeat ok'
334 );
335
336 is(
337   run_for {
338     $_->repeat_content(
339       [
340         sub {
341           filter
342             filter($_ => '.name' => $r_content->('mst'))
343             => '.career' => $r_content->('Chainsaw Wielder')
344         },
345         sub {
346           filter
347             filter($_ => '.name' => $r_content->('mdk'))
348             => '.career' => $r_content->('Adminion')
349         },
350       ]
351     )
352   },
353   q{<body>
354   <div class="main">
355     <span class="hilight name">mst</span>
356     <span class="career">Chainsaw Wielder</span>
357     <hr />
358   
359     <span class="hilight name">mdk</span>
360     <span class="career">Adminion</span>
361     <hr />
362   </div>
363 </body>
364 },
365   'repeat_content ok'
366 );
367
368 is(
369   run_for {
370     my @between;
371     $_->repeat_content(
372       [
373         sub {
374           HTML::Zoom::ArrayStream->new({ array => [
375             (filter
376               filter($_ => '.name' => $r_content->('mst'))
377               => '.career' => $r_content->('Chainsaw Wielder')),
378             HTML::Zoom::ArrayStream->new({ array => \@between })
379           ] })->flatten
380         },
381         sub {
382           filter
383             filter($_ => '.name' => $r_content->('mdk'))
384             => '.career' => $r_content->('Adminion')
385         },
386       ],
387       { filter => sub {
388           filter $_[0] => 'hr' => sub { $_->collect({ into => \@between }) }
389         }
390       }
391     )
392   },
393   q{<body>
394   <div class="main">
395     <span class="hilight name">mst</span>
396     <span class="career">Chainsaw Wielder</span>
397     <hr />
398     <span class="hilight name">mdk</span>
399     <span class="career">Adminion</span>
400     
401   </div>
402 </body>
403 },
404   'repeat_content with filter ok'
405 );
406
407 is(
408   run_for {
409     my @between;
410     $_->repeat_content(
411       [
412         sub {
413           filter
414             filter($_ => '.name' => $r_content->('mst'))
415             => '.career' => $r_content->('Chainsaw Wielder')
416         },
417         sub {
418           filter
419             filter($_ => '.name' => $r_content->('mdk'))
420             => '.career' => $r_content->('Adminion')
421         },
422       ],
423       { repeat_between => 'hr' }
424     )
425   },
426   q{<body>
427   <div class="main">
428     <span class="hilight name">mst</span>
429     <span class="career">Chainsaw Wielder</span>
430     <hr />
431     <span class="hilight name">mdk</span>
432     <span class="career">Adminion</span>
433     
434   </div>
435 </body>
436 },
437   'repeat_content using repeat_between ok'
438 );
439
440 done_testing;