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