June 2015 Archives

Testing and Validating HTML Templates

Here’s a little trick I’ve been using for a while to help ensure that a large sprawling Catalyst application always generates valid HTML. The idea is to unit test all your templates: the trick is to make it really easy with a helper class:

package My::Test::Template;

use strict;
use warnings;

use parent q(Template);

use Hash::Merge::Simple;
use Path::Class qw(dir);
use HTML::Lint::Pluggable;
use Test::More;

sub validate {
    my ( $self, $output ) = @_;

    # Routine to validate HTML5 structure

    # If this looks like an HTML fragment, wrap it in minimal tags
    if ( $output !~ m{^<html[^>]*>}ims ) {

        $output = join(
            "\n",
            '<html><head><title>Title</title></head><body>',
            $output,
            '</body></html>'
        );
    }

    my $lint = HTML::Lint::Pluggable->new();

    $lint->load_plugins('HTML5');
    $lint->only_types(HTML::Lint::Error::STRUCTURE);
    $lint->parse($output);
    $lint->eof;

    my $message = 'output is valid HTML5';
    if ( $lint->errors ) {
        for my $error ( $lint->errors ) {
            warn $error->as_string, "\n";
        }
        fail($message);
    }
    else {
        pass($message);
    }

    return $output;

} ## end sub validate

sub _init {
    my ($self, $config) = @_;

    # Modify the _init() routine from Template:
    #
    #   * add an INPUT parameter so we can specify the template file or string
    #     to test.
    #
    #   * set the default template INCLUDE_PATH and other config options to be
    #     the same as used by our Catalyst view.
    #
    $self->{INPUT} = $config->{INPUT} or die "INPUT parameter is required";

    $config = Hash::Merge::Simple::merge(
        {
            INCLUDE_PATH => [
                dir( $ENV{'PWD'}, '/root/src' )->cleanup,
                dir( $ENV{'PWD'}, '/root/lib' )->cleanup,
            ],
            TRIM        => 1,
            PRE_CHOMP   => 1,
            POST_CHOMP  => 0,
            PRE_PROCESS => 'main.tt2',
            TIMER       => 0,
        },
        $config
    );

    $self = $self->SUPER::_init($config);

    return $self;
}

sub process {
    my ( $self, $vars ) = @_;

    # Modify the process() routine from Template:
    #
    #    * make process() use the INPUT key for the template variable
    #    * die on errors rather than returning an error code
    #    * return the result of successful processing
    #    * always run the validate routine on processing a new template

    my $output = '';
    $self->SUPER::process( $self->{INPUT}, $vars, \$output )
      or die $self->error;

    return $self->validate($output);

} ## end sub process

1;

With this helper class, I can easily unit test the following template:

<div class="subnav_holder">
    <ul class="subnav">
        <li><a href="/faq">FAQ</a></li>
        [% IF has_media %]
        <li><a href="/media">In the media<a></li>
        [% END %]
        <li><a href="/about">About Us</a></li>
        <li><a href="/contact">Contact Us</a></li>
        <li><a href="/legal">Legal</a></li>
    </ul>
</div>

like so:

use strict;
use warnings;

use Test::More;

use My::Test::Template;
my $tt = My::Test::Template->new( { INPUT => 'subnav.tt2', })
    or die "$Template::ERROR\n";

unlike( $tt->process(), qr{media}ms => "Don't display media link" );

like( $tt->process( { has_media => 1 } ), qr{media}ms => 'Display media link' );

done_testing();

When I run the test I get:

ok 1 - output is valid HTML5
ok 2 - Don't display media link
 (5:45) <a> at (5:13) is never closed
 (5:45) <a> at (5:42) is never closed
not ok 3 - output is valid HTML5
#   Failed test 'output is valid HTML5'
#   at root/src/subnav.t line 37.
ok 4 - Display media link
1..4
# Looks like you failed 1 test of 4.

which highlights the HTML validation error that gets exposed if I stash “has_media”.

I really have no excuse if my app generates invalid HTML.

Happy Hacking!

Kal

Hubble, Bubble, Toil and Trouble: Catalyst, Bootstrap and HTML::FormFu

I thought I would share a little trick I use to get these three complex and idiosyncratic frameworks to play nice with each other.

Catalyst and HTML::FormFu are a powerful combination that allows you to tie the form displayed by your view to the form processed by your controller. This direct link means: 1) your field names in your generated HTML will always match the field names used in form processing, 2) default and redisplay field values set by your controller will always match up with the values displayed by the view, and 3) any constraint or validation issues detected by the form processing logic in your controller can be directly associated with the fields displayed by your view. Without this link, keeping a form defined in our view in sync with the form defined by our controller is a large source of potential bugs.

The problematic part of our potion is the combination of HTML::FormFu and Bootstrap. HTML::FormFu is a very powerful and elegant framework for managing forms. It is also very complex and can take some real time and effort to master. It can be a real challenge to get the forms generated by HTML::FormFu to be marked up exactly the way you want. This is problematic when combined with display frameworks like Bootstrap, which can be very fussy about their required markup.

The trick to getting this all to work is best show by example.

Lets start with the following HTML::FormFu form definition:

elements => [
    {
        name => 'min_amount',
        type => 'Text',
    },
    {
        name        => 'max_amount',
        type        => 'DollarAmount',
        constraints => [
            {
                type    => 'GreaterThan',
                others  => 'min_amount',
                message => 'the maximum amount must be greater than the minimum',
            },
        ],
    },
    {
        name  => 'search',
        type  => 'Submit',
    },
]

The corresponding form can be constructed and processed by a Catalyst controller in a number of ways. Ultimately we end up with a form object on the stash which stringifies to an HTML form. This can be use directly in our Template Toolkit view:

[% form %]

The resulting HTML, although lean, is going to be a challenge to style and layout with Bootstrap:

<form action="/my_form" method="post">
    <div class="text">
        <input name="min_amount" type="text" />
    </div>
    <div class="dollaramount">
        <input name="max_amount" type="text" />
    </div>
    <div class="submit">
        <input name="search" type="submit" />
    </div>
</form>

To be fair HTML::FormFu does have a large number of facilities for modifying the generated markup. However, in my experience, they add a lot of complexity to an already complex framework soup. They can also require embedding a lot of display related information in the controller which is not ideal.

An alternative is to construct the form display piece-wise.

First, we strip most of the formating logic from our HTML::FormFu form using the new ‘layout’ feature, and add some Bootstrap classes:

default_args => {
    elements => {
        Field => {
            layout => [ 'field' ],
            attrs  => { class => 'form-control' },
        },
        Submit => {
            attrs => { class => 'form-control btn btn-primary' },
        },
    },
}

(See HTML::FormFu::Role::Element::Field for the gory details of layout)

Now we can markup our form so that it works with Bootstrap:

<form class="form" action=[% form.action %] >
    <div class="row">
        <div class="col-md-2">Lead Amount</div>
        <div class="form-group col-md-4">
            <label for="min_date" class="sr-only">Minimum</label>
            <div class="input-group">
                <div class="input-group-addon">$</div>

                [% form.get_element('min_amount').placeholder('Minimum') %]

            </div>
        </div>
        <div class="form-group col-md-4">
            <label for="max_date" class="sr-only">Maximum</label>
            <div class="input-group">
                <div class="input-group-addon">$</div>

                [% form.get_element('max_amount').placeholder('Maximum') %]

            </div>
        </div>

        <div class="form-group col-md-2">

            [% form.get_element('search').value('Search Leads') %]

        </div>
    </div>

    <div class="row">
        <div class="col col-md-10 col-md-offset-2 text-danger">

        [% FOR error IN form.get_errors %]
            [% error.message | html_line_break %]
        [% END %]

        </div>
    </div>

</form>

The trick here is to note that the form object stringifies recursively. If you pick out an element of the form object, it will stringify to the corresponding HTML field.

[% form.get_element('search') %]

You can also modify the element in place before the stringification takes place:

[% form.get_element('search').value('Search') %]

The result looks something like this:

form-display.png

Magic!

This gives us a much better separation of display and processing concerns. It is now much easier for me to pass off my template to a designer who can easily tweak language and worry about the finer points of styling and layout.

Happy Hacking!

Kal

About Kahlil (Kal) Hodgson

user-pic Leaking memory all over the place.