dwim selects and bugfixes, new transform_attribute method and handled trailing '...
John Napiorkowski [Fri, 22 Apr 2011 17:35:44 +0000 (13:35 -0400)]
updated changes

Changes
lib/HTML/Zoom.pm
lib/HTML/Zoom/FilterBuilder.pm
lib/HTML/Zoom/StreamBase.pm
lib/HTML/Zoom/StreamUtils.pm
t/dwim-autoload.t [new file with mode: 0644]
t/repeat_bugfixes.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 39d4d4f..ef24931 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,5 +1,19 @@
-  - add transform_attribute method (rbuels)
+TBA
+  - New Feature:  HTML::Zoom will autoload FilterBuilder functions so that
+    you can avoid a bit of boilerplate in method calls.  Now you can replace:
+
+        $z->select('div')->replace_content("Hello World");
+
+    With:
+
+        $z->replace_content(div => "Hello World");
+
+  - Lots of changes to FilterBuilder so that functionality matched the docs
+    better, improved the docs and added tests for documented functions to
+    avoid future regressions.
+
   - add / to excluded characters in attribute names to correctly parse <br/> 
+  - add transform_attribute method (rbuels)
 
 0.009004 2011-02-14
 
index 402ca1f..5a852ea 100644 (file)
@@ -6,6 +6,7 @@ use HTML::Zoom::ZConfig;
 use HTML::Zoom::ReadFH;
 use HTML::Zoom::Transform;
 use HTML::Zoom::TransformBuilder;
+use Scalar::Util ();
 
 our $VERSION = '0.009004';
 
@@ -149,6 +150,18 @@ sub then {
   $self->select($self->{transforms}->[-1]->selector);
 }
 
+sub AUTOLOAD {
+  my ($self, $selector, @args) = @_;
+  my $sel = $self->select($selector);
+  my $meth = our $AUTOLOAD;
+  $meth =~ s/.*:://;
+  if(my $cr = $sel->_zconfig->filter_builder->can($meth)) {
+    return $sel->$meth(@args);
+  } else {
+    die "We can't do $meth on ->select('$selector')";
+  }
+}
+
 1;
 
 =head1 NAME
@@ -717,6 +730,20 @@ together; the intermediary object isn't designed or expected to stick around.
 Re-runs the previous select to allow you to chain actions together on the
 same selector.
 
+=head1 AUTOLOAD METHODS
+
+L<HTML::Zoom> AUTOLOADS methods against L</select> so that you can reduce a
+certain amount of boilerplate typing.  This allows you to replace:
+
+  $z->select('div')->replace_content("Hello World");
+  
+With:
+
+  $z->replace_content(div => "Hello World");
+  
+Besides saving a few keys per invocations, you may feel this looks neater
+in your code and increases understanding.
+
 =head1 AUTHOR
 
 mst - Matt S. Trout (cpan:MSTROUT) <mst@shadowcat.co.uk>
index 45b57f2..6577fd9 100644 (file)
@@ -187,7 +187,21 @@ sub collect_content {
 
 sub add_before {
   my ($self, $events) = @_;
-  sub { return $self->_stream_from_array(@$events, $_[0]) };
+  my $coll_proto = $self->collect({ passthrough => 1 });
+  sub {
+    my $emit = $self->_stream_from_proto($events);
+    my $coll = &$coll_proto;
+    if($coll) {
+      if(ref $coll eq 'ARRAY') {
+        my $firstbit = $self->_stream_from_proto([$coll->[0]]);
+        return $self->_stream_concat($emit, $firstbit, $coll->[1]);
+      } elsif(ref $coll eq 'HASH') {
+        return [$emit, $coll];
+      } else {
+        return $self->_stream_concat($emit, $coll);
+      }
+    } else { return $emit }
+  }
 }
 
 sub add_after {
@@ -195,7 +209,7 @@ sub add_after {
   my $coll_proto = $self->collect({ passthrough => 1 });
   sub {
     my ($evt) = @_;
-    my $emit = $self->_stream_from_array(@$events);
+    my $emit = $self->_stream_from_proto($events);
     my $coll = &$coll_proto;
     return ref($coll) eq 'HASH' # single event, no collect
       ? [ $coll, $emit ]
@@ -205,15 +219,18 @@ sub add_after {
 
 sub prepend_content {
   my ($self, $events) = @_;
+  my $coll_proto = $self->collect({ passthrough => 1, content => 1 });
   sub {
     my ($evt) = @_;
+    my $emit = $self->_stream_from_proto($events);
     if ($evt->{is_in_place_close}) {
       $evt = { %$evt }; delete @{$evt}{qw(raw is_in_place_close)};
       return [ $evt, $self->_stream_from_array(
-        @$events, { type => 'CLOSE', name => $evt->{name} }
+        $emit->next, { type => 'CLOSE', name => $evt->{name} }
       ) ];
     }
-    return $self->_stream_from_array($evt, @$events);
+    my $coll = &$coll_proto;
+    return [ $coll->[0], $self->_stream_concat($emit, $coll->[1]) ];
   };
 }
 
@@ -222,14 +239,14 @@ sub append_content {
   my $coll_proto = $self->collect({ passthrough => 1, content => 1 });
   sub {
     my ($evt) = @_;
+    my $emit = $self->_stream_from_proto($events);
     if ($evt->{is_in_place_close}) {
       $evt = { %$evt }; delete @{$evt}{qw(raw is_in_place_close)};
       return [ $evt, $self->_stream_from_array(
-        @$events, { type => 'CLOSE', name => $evt->{name} }
+        $emit->next, { type => 'CLOSE', name => $evt->{name} }
       ) ];
     }
     my $coll = &$coll_proto;
-    my $emit = $self->_stream_from_array(@$events);
     return [ $coll->[0], $self->_stream_concat($coll->[1], $emit) ];
   };
 }
@@ -287,7 +304,7 @@ sub repeat {
   if ($repeat_between) {
     $options->{filter} = sub {
       $_->select($repeat_between)->collect({ into => \@between })
-    };
+    }
   }
   my $repeater = sub {
     my $s = $self->_stream_from_proto($repeat_for);
@@ -433,7 +450,7 @@ Sets an attribute of a given name to a given value for all matching selections.
       ->select('p')
       ->set_attribute(class=>'paragraph')
       ->select('div')
-      ->set_attribute(name=>'class', value=>'divider');
+      ->set_attribute({name=>'class', value=>'divider'});
 
 
 Overrides existing values, if such exist.  When multiple L</set_attribute>
@@ -443,13 +460,23 @@ call wins.
 =head2 add_to_attribute
 
 Adds a value to an existing attribute, or creates one if the attribute does not
-yet exist.
+yet exist.  You may call this method with either an Array or HashRef of Args.
+
+Here's the 'long form' HashRef:
+
+    $html_zoom
+      ->select('p')
+      ->set_attribute(class=>'paragraph')
+      ->then
+      ->add_to_attribute({name=>'class', value=>'divider'});
+
+And the exact same effect using the 'short form' Array:
 
     $html_zoom
       ->select('p')
       ->set_attribute(class=>'paragraph')
       ->then
-      ->add_to_attribute(name=>'class', value=>'divider');
+      ->add_to_attribute(class=>'divider');
 
 Attributes with more than one value will have a dividing space.
 
@@ -609,11 +636,31 @@ You can add zoom events directly
 
 =head2 prepend_content
 
-    TBD
+Similar to add_before, but adds the content to the match.
+
+  HTML::Zoom
+    ->from_html(q[<p>World</p>])
+    ->select('p')
+    ->prepend_content("Hello ")
+    ->to_html
+    
+  ## <p>Hello World</p>
+  
+Acceptable values are strings, scalar refs and L<HTML::Zoom> objects
 
 =head2 append_content
 
-    TBD
+Similar to add_after, but adds the content to the match.
+
+  HTML::Zoom
+    ->from_html(q[<p>Hello </p>])
+    ->select('p')
+    ->prepend_content("World")
+    ->to_html
+    
+  ## <p>Hello World</p>
+
+Acceptable values are strings, scalar refs and L<HTML::Zoom> objects
 
 =head2 replace
 
@@ -632,34 +679,12 @@ or another L<HTML::Zoom> object.
 
 =head2 repeat
 
-    $zoom->select('.item')->repeat(sub {
-      if (my $row = $db_thing->next) {
-        return sub { $_->select('.item-name')->replace_content($row->name) }
-      } else {
-        return
-      }
-    }, { flush_before => 1 });
-
-Run I<$repeat_for>, which should be iterator (code reference) returning
-subroutines, reference to array of subroutines, or other zoom-able object
-consisting of transformations.  Those subroutines would be run with $_
-local-ized to result of L<HTML::Zoom/select> (of collected elements), and with
-said result passed as parameter to subroutine.
+For a given selection, repeat over transformations, typically for the purposes
+of populating lists.  Takes either an array of anonymous subroutines or a zoom-
+able object consisting of transformation.
 
-You might want to use iterator when you don't have all elements upfront
-
-    $zoom = $zoom->select('.contents')->repeat(sub {
-      while (my $line = $fh->getline) {
-        return sub {
-          $_->select('.lno')->replace_content($fh->input_line_number)
-            ->select('.line')->replace_content($line)
-        }
-      }
-      return
-    });
-
-You might want to use array reference if it doesn't matter that all iterations
-are pre-generated
+Example of array reference style (when it doesn't matter that all iterations are
+pre-generated)
 
     $zoom->select('table')->repeat([
       map {
@@ -669,8 +694,27 @@ are pre-generated
         }
       } @list
     ]);
+    
+Subroutines would be run with $_ localized to result of L<HTML::Zoom/select> (of
+collected elements), and with said result passed as parameter to subroutine.
+
+You might want to use CodeStream when you don't have all elements upfront
+
+    $zoom->select('.contents')->repeat(sub {
+      HTML::Zoom::CodeStream->new({
+        code => sub {
+          while (my $line = $fh->getline) {
+            return sub {
+              $_->select('.lno')->replace_content($fh->input_line_number)
+                ->select('.line')->replace_content($line)
+            }
+          }
+          return
+        },
+      })
+    });
 
-In addition to common options as in L</collect>, it also supports
+In addition to common options as in L</collect>, it also supports:
 
 =over
 
index a22158b..8fa8d53 100644 (file)
@@ -98,4 +98,11 @@ sub to_html {
   $self->_zconfig->producer->html_from_stream($self);
 }
 
+sub AUTOLOAD {
+  my ($self, $selector, @args) = @_;
+  my $meth = our $AUTOLOAD;
+  $meth =~ s/.*:://;
+  return $self = $self->select($selector)->$meth(@args);
+}
+
 1;
index ff42fa6..2a333f7 100644 (file)
@@ -2,7 +2,6 @@ package HTML::Zoom::StreamUtils;
 
 use strictures 1;
 use base qw(HTML::Zoom::SubObject);
-
 use Scalar::Util ();
 
 use HTML::Zoom::CodeStream;
diff --git a/t/dwim-autoload.t b/t/dwim-autoload.t
new file mode 100644 (file)
index 0000000..54de2fd
--- /dev/null
@@ -0,0 +1,325 @@
+use strict;
+use warnings FATAL => 'all';
+
+use HTML::Zoom;
+use Test::More;
+
+ok my $zoom = HTML::Zoom->from_html(<<HTML);
+<html>
+  <head>
+    <title>Hi!</title>
+  </head>
+  <body id="content-area">
+    <h1>Test</h1>
+    <div>
+      <p class="first-para">Some Stuff</p>
+      <p class="body-para">More Stuff</p>
+      <p class="body-para">Even More Stuff</p>
+      <ol>
+        <li class="first-item odd">First</li>
+        <li class="body-items even">Stuff A</li>
+        <li class="body-items odd">Stuff B</li>
+        <li class="body-items even">Stuff C</li>
+        <li class="last-item odd">Last</li>
+      </ol>
+      <ul class="items">
+        <li class="first">space</li>
+        <li class="second">space</li>
+      </ul>
+      <p class="body-para">Even More stuff</p>
+      <p class="last-para">Some Stuff</p>
+    </div>
+    <div id="blocks">
+      <h2 class="h2-item">Sub Item</h2>
+    </div>
+    <ul id="people">
+        <li class="name">name</li>
+        <li class="email">email</li>
+    </ul>
+    <ul id="people2">
+        <li class="name">name</li>
+        <li class="email">email</li>
+    </ul>
+    <ul id="object">
+        <li class="aa">AA</li>
+        <li class="bb">BB</li>
+    </ul>
+    <p id="footer">Copyright 2222</p>
+  </body>
+</html>
+HTML
+
+ok ! eval { $zoom->non_method('.first-param' => 'First!'); 1},
+  'Properly die on bad method';
+
+ok my $title = sub {
+  my ($z, $content) = @_;
+  $z = $z->replace_content(title =>$content);
+  return $z;
+};
+
+ok my $dwim = $zoom
+  ->$title('Hello NYC')
+  ->replace_content('.first-para' => 'First!')
+  ->replace_content('.last-para' => 'Last!')
+  ->add_to_attribute(p => class => 'para')
+  ->prepend_content('.first-item' => [{type=>'TEXT', raw=>'FIRST: '}])
+  ->prepend_content('.last-item' => [{type=>'TEXT', raw=>'LAST: '}])
+  ->replace_content('title' => 'Hello World')
+  ->repeat_content(
+    '.items' => [ 
+      map { 
+        my $v = $_;
+        sub {
+          $_->replace_content('.first' => $v)
+            ->replace_content('.second' => $v);
+        },
+      } (111,222)
+    ]
+  )
+  ->to_html;
+
+like(
+  HTML::Zoom
+    ->from_html(q[<title>Hi!</title>])
+    ->$title('Hello NYC')
+    ->to_html,
+  qr/Hello NYC/,
+  'Got correct title(custom subref)'
+);
+
+like(
+  HTML::Zoom
+    ->from_html(q[<p>Hi!</p>])
+    ->replace_content(p=>'Ask not what your country can do for you...')
+    ->to_html,
+  qr/Ask not what your country can do for you\.\.\./,
+  'Got correct from replace_content'
+);
+
+like(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->add_to_attribute('p', {name => 'class', value => 'para'})
+    ->to_html,
+  qr/first para/,
+  'Got correct from add_to_attribute'
+);
+
+like(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->add_to_attribute('p', class => 'para')
+    ->to_html,
+  qr/first para/,
+  'Got correct from add_to_attribute'
+);
+
+like(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->set_attribute('p', class => 'para')
+    ->to_html,
+  qr/class="para"/,
+  'Got correct from set_attribute'
+);
+
+like(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->set_attribute('p', {name => 'class', value => 'para'})
+    ->to_html,
+  qr/class="para"/,
+  'Got correct from set_attribute'
+);
+
+is(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->add_before(p=>[{type=>'TEXT', raw=>'added before'}])
+    ->to_html,
+  'added before<p class="first">Hi!</p>',
+  'Got correct from add_before'
+);
+
+is(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->add_after(p=>[{type=>'TEXT', raw=>'added after'}])
+    ->to_html,
+  '<p class="first">Hi!</p>added after',
+  'Got correct from add_after'
+);
+
+is(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->prepend_content(p=>[{type=>'TEXT', raw=>'prepend_content'}])
+    ->to_html,
+  '<p class="first">prepend_contentHi!</p>',
+  'Got correct from prepend_content'
+);
+
+is(
+  HTML::Zoom
+    ->from_html(q[<p class="first">Hi!</p>])
+    ->append_content(p=>[{type=>'TEXT', raw=>'append_content'}])
+    ->to_html,
+  '<p class="first">Hi!append_content</p>',
+  'Got correct from append_content'
+);
+
+{
+    my @body;
+    ok my $body = HTML::Zoom
+      ->from_html(q[<div>My Stuff Is Here</div>])
+      ->collect_content(div => { into => \@body, passthrough => 1})
+      ->to_html, 'Collected Content';
+
+    is $body, "<div>My Stuff Is Here</div>", "Got expected";
+
+    is(
+      HTML::Zoom->from_events(\@body)->to_html,
+      'My Stuff Is Here',
+      'Collected the right stuff',
+    );
+}
+
+like(
+  HTML::Zoom
+    ->from_html(q[<ul class="items"><li class="first">first</li><li class="second">second</li></ul>])
+    ->repeat_content(
+      '.items' => [ 
+        map { 
+          my $v = $_;
+          sub {
+            $_->replace_content('.first' => $v)
+              ->replace_content('.second' => $v);
+          },
+        } (111,222)
+      ]
+    )
+    ->to_html,
+  qr[<ul class="items"><li class="first">111</li><li class="second">111</li><li class="first">222</li><li class="second">222</li></ul>],
+  'Got correct list'
+);
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+    ->add_before(p=>'added before');
+
+  is $z->to_html, '<div>Life, whatever</div>added before<p class="first">Hi!</p>',
+    'Got correct from add_before';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+    ->add_after(p=>'added after');
+
+  is $z->to_html, '<div>Life, whatever</div><p class="first">Hi!</p>added after',
+    'Got correct from add_after';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+    ->append_content(p=>'appended');
+
+  is $z->to_html, '<div>Life, whatever</div><p class="first">Hi!appended</p>',
+    'Got correct from append_content';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<div>Life, whatever</div><p class="first">Hi!</p>])
+    ->prepend_content(p=>'prepended');
+    
+
+  is $z->to_html, '<div>Life, whatever</div><p class="first">prependedHi!</p>',
+    'Got correct from prepend_content';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(
+      [
+        sub { $_->select('li')->replace_content('Real Life1') },
+        sub { $_->select('li')->replace_content('Real Life2') },
+        sub { $_->select('li')->replace_content('Real Life3') },
+      ],
+    )
+    ->to_html;
+
+  is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content([
+      map { my $i = $_; +sub {$_->select('li')->replace_content("Real Life$i")} } (1,2,3)
+    ])
+    ->to_html;
+
+  is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>',
+    'Got correct from repeat_content';
+}
+
+
+use HTML::Zoom::CodeStream;
+sub code_stream (&) {
+  my $code = shift;
+  return sub {
+    HTML::Zoom::CodeStream->new({
+      code => $code,
+    });
+  }
+}
+
+{
+  my @list = qw(foo bar baz);
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(code_stream {
+      if (my $name = shift @list) {
+        return sub { $_->select('li')->replace_content($name) };
+      } else {
+        return
+      }
+    })
+    ->to_html;
+  
+  is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  my @list = qw(foo bar baz);
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(sub {
+      HTML::Zoom::CodeStream->new({
+        code => sub {
+          if (my $name = shift @list) {
+            return sub { $_->select('li')->replace_content($name) };
+          } else {
+            return
+          }
+        }
+      });
+    })
+    ->to_html;
+  
+  is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>',
+    'Got correct from repeat_content';
+}
+
+done_testing;
diff --git a/t/repeat_bugfixes.t b/t/repeat_bugfixes.t
new file mode 100644 (file)
index 0000000..4684b47
--- /dev/null
@@ -0,0 +1,141 @@
+use strict;
+use warnings FATAL => 'all';
+
+use HTML::Zoom;
+use Test::More;
+
+ok my $zoom = HTML::Zoom->from_html(<<HTML);
+<html>
+  <head>
+    <title>Hi!</title>
+  </head>
+  <body id="content-area">
+    <h1>Test</h1>
+    <div>
+      <p class="first-para">Some Stuff</p>
+      <p class="body-para">More Stuff</p>
+      <p class="body-para">Even More Stuff</p>
+      <ol>
+        <li class="first-item odd">First</li>
+        <li class="body-items even">Stuff A</li>
+        <li class="body-items odd">Stuff B</li>
+        <li class="body-items even">Stuff C</li>
+        <li class="last-item odd">Last</li>
+      </ol>
+      <ul class="items">
+        <li class="first">space</li>
+        <li class="second">space</li>
+      </ul>
+      <p class="body-para">Even More stuff</p>
+      <p class="last-para">Some Stuff</p>
+    </div>
+    <div id="blocks">
+      <h2 class="h2-item">Sub Item</h2>
+    </div>
+    <ul id="people">
+        <li class="name">name</li>
+        <li class="email">email</li>
+    </ul>
+    <ul id="people2">
+        <li class="name">name</li>
+        <li class="email">email</li>
+    </ul>
+    <ul id="object">
+        <li class="aa">AA</li>
+        <li class="bb">BB</li>
+    </ul>
+    <p id="footer">Copyright 2222</p>
+  </body>
+</html>
+HTML
+
+## Stub for testing the fill method to be
+
+ok my $title = sub {
+  my ($z, $content) = @_;
+  $z = $z->select('title')->replace_content($content);
+  return $z;
+};
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(
+      [
+        sub { $_->select('li')->replace_content('Real Life1') },
+        sub { $_->select('li')->replace_content('Real Life2') },
+        sub { $_->select('li')->replace_content('Real Life3') },
+      ],
+    )
+    ->to_html;
+
+  is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content([
+      map { my $i = $_; +sub {$_->select('li')->replace_content("Real Life$i")} } (1,2,3)
+    ])
+    ->to_html;
+
+  is $z, '<ul><li>Real Life1</li><li>Real Life2</li><li>Real Life3</li></ul>',
+    'Got correct from repeat_content';
+}
+
+
+use HTML::Zoom::CodeStream;
+sub code_stream (&) {
+  my $code = shift;
+  return sub {
+    HTML::Zoom::CodeStream->new({
+      code => $code,
+    });
+  }
+}
+
+{
+  my @list = qw(foo bar baz);
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(code_stream {
+      if (my $name = shift @list) {
+        return sub { $_->select('li')->replace_content($name) };
+      } else {
+        return
+      }
+    })
+    ->to_html;
+  
+  is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>',
+    'Got correct from repeat_content';
+}
+
+{
+  my @list = qw(foo bar baz);
+  ok my $z = HTML::Zoom
+    ->from_html(q[<ul><li>Test</li></ul>])
+    ->select('ul')
+    ->repeat_content(sub {
+      HTML::Zoom::CodeStream->new({
+        code => sub {
+          if (my $name = shift @list) {
+            return sub { $_->select('li')->replace_content($name) };
+          } else {
+            return
+          }
+        }
+      });
+    })
+    ->to_html;
+  
+  is $z, '<ul><li>foo</li><li>bar</li><li>baz</li></ul>',
+    'Got correct from repeat_content';
+}
+
+done_testing;