The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strictures 1;
use Test::More;

use HTML::Zoom::Parser::BuiltIn;
use HTML::Zoom::Producer::BuiltIn;
use HTML::Zoom::SelectorParser;
use HTML::Zoom::FilterBuilder;
use HTML::Zoom::FilterStream;

my $tmpl = <<END;
<body>
  <div class="main">
    <span class="hilight name">Bob</span>
    <span class="career">Builder</span>
    <hr />
  </div>
</body>
END

sub src_stream { HTML::Zoom::Parser::BuiltIn->new->html_to_stream($tmpl); }

sub html_sink { HTML::Zoom::Producer::BuiltIn->new->html_from_stream($_[0]) }

my $fb = HTML::Zoom::FilterBuilder->new;

my $sp = HTML::Zoom::SelectorParser->new;

sub filter {
  my ($stream, $sel, $cb) = @_;
  return HTML::Zoom::FilterStream->new({
    stream => $stream,
    match => $sp->parse_selector($sel),
    filter => do { local $_ = $fb; $cb->($fb) }
  });
}

sub run_for (&;$) {
  my $cb = shift;
  (html_sink
    (filter
      src_stream,
      (shift or '.main'),
      $cb
    )
  )
}

my ($expect, @ev);

($expect = $tmpl) =~ s/class="main"/class="foo"/;

is(
  run_for { $_->set_attribute({ name => 'class', value => 'foo' }) },
  $expect,
  'set attribute on existing attribute'
);

($expect = $tmpl) =~ s/class="main"/class="main" foo="bar"/;

is(
  run_for { $_->set_attribute({ name => 'foo', value => 'bar' }) },
  $expect,
  'set attribute on non existing attribute'
);

($expect = $tmpl) =~ s/class="main"/class="main foo"/;

is(
  run_for { $_->add_to_attribute({ name => 'class', value => 'foo' }) },
  $expect,
  'add attribute on existing attribute'
);

($expect = $tmpl) =~ s/class="main"/class="main" foo="bar"/;

is(
  run_for { $_->add_to_attribute({ name => 'foo', value => 'bar' }) },
  $expect,
  'add attribute on non existing attribute'
);

($expect = $tmpl) =~ s/ class="main"//;

is(
  run_for { $_->remove_attribute({ name => 'class' }) },
  $expect,
  'remove attribute on existing attribute'
);

is(
  run_for { $_->remove_attribute({ name => 'foo' }) },
  $tmpl,
  'remove attribute on non existing attribute'
);

($expect = $tmpl) =~ s/ class="main"//;

is(
  run_for {
      $_->transform_attribute({
          name => 'class',
          code => sub {
              my $a = shift;
              return if $a eq 'main';
              return $a;
          },
      })
  },
  $expect,
  'transform_attribute deletes the attr if code returns undef',
  );

($expect = $tmpl) =~ s/ class="main"/ class="moan"/;

is(
  run_for {
      $_->transform_attribute({
          name => 'class',
          code => sub {
              ( my $b = shift ) =~ s/main/moan/;
              $b
          },
      })
  },
  $expect,
  'transform_attribute transforms something',
  );

($expect = $tmpl) =~ s/ class="main"/ class="main" noggin="zonk"/;

is(
  run_for {
      $_->transform_attribute({
          name => 'noggin',
          code => sub { 'zonk' },
      })
  },
  $expect,
  'transform_attribute adds attribute if not there before',
  );

is(
  run_for {
      $_->transform_attribute({
          name => 'noggin',
          code => sub { },
      })
  },
  $tmpl,
  'transform_attribute on nonexistent att does not add it if code returns undef',
  );


($expect = $tmpl) =~ s/(?=<div)/O HAI/;

my $ohai = [ { type => 'TEXT', raw => 'O HAI' } ];

is(
  run_for { $_->add_before($ohai) },
  $expect,
  'add_before ok'
);

($expect = $tmpl) =~ s/(?<=<\/div>)/O HAI/;

is(
  run_for { $_->add_after($ohai) },
  $expect,
  'add_after ok'
);

($expect = $tmpl) =~ s/(?<=class="main">)/O HAI/;

is(
  run_for { $_->prepend_content($ohai) },
  $expect,
  'prepend_content ok'
);

($expect = $tmpl) =~ s/<hr \/>/<hr>O HAI<\/hr>/;

is(
  (run_for { $_->prepend_content($ohai) } 'hr'),
  $expect,
  'prepend_content ok with in place close'
);

is(
  run_for { $_->replace($ohai) },
'<body>
  O HAI
</body>
',
  'replace ok'
);

@ev = ();

is(
  run_for { $_->collect({ into => \@ev }) },
  '<body>
  
</body>
',
  'collect removes without passthrough'
);

is(
  HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
  '<div class="main">
    <span class="hilight name">Bob</span>
    <span class="career">Builder</span>
    <hr />
  </div>',
  'collect collected right events'
);

@ev = ();

is(
  run_for { $_->collect({ into => \@ev, content => 1 }) },
  '<body>
  <div class="main"></div>
</body>
',
  'collect w/content removes correctly'
);

is(
  HTML::Zoom::Producer::BuiltIn->html_from_events(\@ev),
  '
    <span class="hilight name">Bob</span>
    <span class="career">Builder</span>
    <hr />
  ',
  'collect w/content collects correctly'
);

is(
  run_for { $_->replace($ohai, { content => 1 }) },
  '<body>
  <div class="main">O HAI</div>
</body>
',
  'replace w/content'
);

($expect = $tmpl) =~ s/(?=<\/div>)/O HAI/;

is(
  run_for { $_->append_content($ohai) },
  $expect,
  'append content ok'
);

my $r_content = sub { my $r = shift; sub { $_->replace($r, { content => 1 }) } };

is(
  run_for {
    $_->repeat(
      [
        sub {
          filter
            filter($_ => '.name' => $r_content->('mst'))
            => '.career' => $r_content->('Chainsaw Wielder')
        },
        sub {
          filter
            filter($_ => '.name' => $r_content->('mdk'))
            => '.career' => $r_content->('Adminion')
        },
      ]
    )
  },
  q{<body>
  <div class="main">
    <span class="hilight name">mst</span>
    <span class="career">Chainsaw Wielder</span>
    <hr />
  </div><div class="main">
    <span class="hilight name">mdk</span>
    <span class="career">Adminion</span>
    <hr />
  </div>
</body>
},
  'repeat ok'
);

is(
  run_for {
    $_->repeat_content(
      [
        sub {
          filter
            filter($_ => '.name' => $r_content->('mst'))
            => '.career' => $r_content->('Chainsaw Wielder')
        },
        sub {
          filter
            filter($_ => '.name' => $r_content->('mdk'))
            => '.career' => $r_content->('Adminion')
        },
      ]
    )
  },
  q{<body>
  <div class="main">
    <span class="hilight name">mst</span>
    <span class="career">Chainsaw Wielder</span>
    <hr />
  
    <span class="hilight name">mdk</span>
    <span class="career">Adminion</span>
    <hr />
  </div>
</body>
},
  'repeat_content ok'
);

is(
  run_for {
    my @between;
    $_->repeat_content(
      [
        sub {
          HTML::Zoom::ArrayStream->new({ array => [
            (filter
              filter($_ => '.name' => $r_content->('mst'))
              => '.career' => $r_content->('Chainsaw Wielder')),
            HTML::Zoom::ArrayStream->new({ array => \@between })
          ] })->flatten
        },
        sub {
          filter
            filter($_ => '.name' => $r_content->('mdk'))
            => '.career' => $r_content->('Adminion')
        },
      ],
      { filter => sub {
          filter $_[0] => 'hr' => sub { $_->collect({ into => \@between }) }
        }
      }
    )
  },
  q{<body>
  <div class="main">
    <span class="hilight name">mst</span>
    <span class="career">Chainsaw Wielder</span>
    <hr />
    <span class="hilight name">mdk</span>
    <span class="career">Adminion</span>
    
  </div>
</body>
},
  'repeat_content with filter ok'
);

is(
  run_for {
    my @between;
    $_->repeat_content(
      [
        sub {
          filter
            filter($_ => '.name' => $r_content->('mst'))
            => '.career' => $r_content->('Chainsaw Wielder')
        },
        sub {
          filter
            filter($_ => '.name' => $r_content->('mdk'))
            => '.career' => $r_content->('Adminion')
        },
      ],
      { repeat_between => 'hr' }
    )
  },
  q{<body>
  <div class="main">
    <span class="hilight name">mst</span>
    <span class="career">Chainsaw Wielder</span>
    <hr />
    <span class="hilight name">mdk</span>
    <span class="career">Adminion</span>
    
  </div>
</body>
},
  'repeat_content using repeat_between ok'
);

done_testing;