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