NAME

Test2::Tools::DOM - Tools to test HTML/XML-based DOM representations

SYNOPSIS

use Test2::V0;
use Test2::Tools::DOM;

my $html = <<'HTML';
<!DOCTYPE html>
<html lang="en-US">
    <head>
        <title>A test document</title>
        <link rel="icon" href="favicon.ico">
    </head>
    <body>
        <p class="paragraph">Some text</p>
    </body>
</html>
HTML

is $html, dom {
    children bag {
        item dom { tag 'body' };
        item dom { tag 'head' };
        end;
    };

    at 'link[rel=icon]' => dom {
        attr href => 'favicon.ico'
    };

    find '.paragraph' => array {
        item dom { text 'Some text' };
        end;
    };
};

done_testing;

DESCRIPTION

Test2::Tools::DOM exports a set of testing functions designed to make it easier to write declarative tests for XML-based DOM representations. This will most commonly be HTML documents, but it can include other similar types of documents (eg. SVG images, other XML documents, etc).

FUNCTIONS

The functions described in this section are exported by default by this distribution.

Most of the heavy lifting behind the scenes is done by Mojo::DOM58, and most of the functions described below are thin wrappers around the methods in that class with the same names.

Likewise, several of them support CSS selectors for filtering the elements they will return.

Please refer to that distribution's documentation for additional details.

dom

dom { ... }

Starts a new DOM testing context. It takes a single block, inside which the rest of the functions described in this section can be used.

It can be used as the check in any Test2 testing method.

The input can either be a Mojo::DOM58 object, or a string with the text representation of the DOM, which will be passed to the Mojo::DOM58 constructor.

For convenience, if the input is at the root node of the DOM tree, it will be advanced to its first child element, if one exists.

all_text

all_text CHECK

Takes a check only. Extracts the text content from all descendants of this element (by calling all_text on the Mojo::DOM58 object), and this is passed to the provided check.

is '<p>Hello, <em>World!</em></p>', dom {
    all_text 'Hello, World!'; # OK: includes text in descendants

    text 'Hello, '; # OK: use text for the text of this element only
};

at

at SELECTOR, CHECK

Takes a selector and a check. The selector is used to find the first matching descendant (by calling at on the Mojo::DOM58 object), and this is passed to the provided check.

The Test2 existence checks can be used to check whether a given selector matches or not.

is '<div id=a><div id=b></div></div>', dom {
    attr id => 'a'; # OK, we start at #a

    at '#b' => dom {
        attr id => 'b'; # OK, we've moved to #b
    };

    at '#c' => DNE; # OK, this element does not exist
                    # A missing element matches U, F, and DNE
                    # A present element matches D, T, and E
};

attr

attr CHECK
attr NAME, CHECK

Takes either a single check, or the name of an attribute and a check.

When called without a name, all attributes are fetched and passed to the check as a hashref (by calling attr on the Mojo::DOM58 object), and this is passed to the provided check.

When called with a name, only the attribute with that name will be read and passed to the check.

is '<input type=checkbox name=answer value=42 checked>', dom {
    # Get a hashref with all attributes
    # Hashref is then checked using standard Perl logic
    attr hash {
        field type  => 'checkbox';
        field name  => 'answer';
        field value => 42;

        field checked => E; # OK: the attribute exists
        field checked => U; # OK: the attribute has no value
        field checked => F; # OK: undefined is false in Perl-land
        end;
    };
};

When fetching a single value, the Test2 boolean and existence checks will be interpreted using XML-logic rather than Perl-logic: an attribute without a value in the DOM will be undefined but true.

is '<input type=checkbox name=answer value=42 checked>', dom {
    attr type    => 'checkbox';
    attr name    => 'answer';
    attr value   => 42;

    # When fetching individual attributes, checks use XML-logic
    attr checked => E; # OK: the attribute exists
    attr checked => U; # OK: the attribute has no value, so it's undefined
    attr checked => T; # OK: the attribute is present, so it's true
};

children

children CHECK
children SELECTOR, CHECK

Takes either a single check, or a selector and a check.

When called without a selector, all direct children of the current element will be passed to the check as a possibly empty arrayref (by calling children on the Mojo::DOM58 object).

When called with a selector, only children that match will be passed to the check.

is '<div><p>Text</p><ol><li>A</li><li>B</li></ol></div>', dom {
    children [
        # First child is <p>
        dom { tag 'p' },

        # Second child is <ol>
        dom {
            tag 'ol';

            children li => [
                dom { text 'A' },
                dom { text 'B' },
            ];
        },
    ];
};

content

content CHECK

Takes a check only. Extracts the raw content from this element and all its descendants (by calling content on the Mojo::DOM58 object), and this is passed to the provided check.

is '<div>Hello, <em>World!</em></div>', dom {
    content 'Hello, <em>World!</em>';

    at em => dom { content 'World!' };
};

find

find SELECTOR, CHECK

Takes a selector and a check. The selector will be used to find all the matching descendants of this elements, which will be passed to the check as a possibly empty arrayref (by calling find on the Mojo::DOM58 object).

is '<div><p>A</p><div><p>B</p><div><p>C</p></div></div></div>', dom {
    # Find all matching direct and indirect children
    find p => [
        dom { text 'A' },
        dom { text 'B' },
        dom { text 'C' },
    ];
};

tag

tag CHECK

Takes a check only. Extracts the tag of the current element (by calling tag on the Mojo::DOM58 object), and this is passed to the provided check.

is '<p></p>', dom { tag 'p' };

text

text CHECK

Takes a check only. Extracts the text content from this element only (by calling text on the Mojo::DOM58 object), and this is passed to the provided check.

is '<p>Hello, <em>World!</em></p>', dom {
    text 'Hello, '; # OK: 'World!' is not in this element

    all_text 'Hello, World!'; # OK: use all_text for descendants' text
};

val

val CHECK

Available from version 0.004.

Takes a check only. Extracts the value from this element (by calling 'val' on the Mojo::DOM58 object), and this is passed to the provided check.

is '<input type=checkbox name=answer value=42>', dom {
    val 42;

    attr value => 42; # The same, but longer
};

SEE ALSO

Test2::Tools::HTTP

A perfect companion to this distribution: Test2::Tools::HTTP supports the requests, Test2::Tools::DOM can be used to check the responses.

Test2::MojoX

If you are used to using Test::Mojo and are looking for a way to use it with the Test2 suite, then this distribution might be the right one for your needs.

COPYRIGHT AND LICENSE

Copyright 2022 José Joaquín Atria

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.