The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

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
    };

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.