# 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](https://metacpan.org/pod/Mojo%3A%3ADOM58), 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](https://metacpan.org/pod/Mojo::DOM58#SELECTORS)
for filtering the elements they will return.

Please refer to [that distribution's documentation](https://metacpan.org/pod/Mojo%3A%3ADOM58) 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](https://metacpan.org/pod/Test2) testing method.

The input can either be a [Mojo::DOM58](https://metacpan.org/pod/Mojo%3A%3ADOM58) object, or a string with the text
representation of the DOM, which will be passed to the [Mojo::DOM58](https://metacpan.org/pod/Mojo%3A%3ADOM58)
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](https://metacpan.org/pod/Mojo%3A%3ADOM58#all_text)), 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](https://metacpan.org/pod/Mojo%3A%3ADOM58#at)), and
this is passed to the provided check.

The
[Test2 existence checks](https://metacpan.org/pod/Test2::Tools::Compare/QUICK-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](https://metacpan.org/pod/Mojo%3A%3ADOM58#attr)), 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](https://metacpan.org/pod/Test2::Tools::Compare/QUICK-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](https://metacpan.org/pod/Mojo%3A%3ADOM58#children)).

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](https://metacpan.org/pod/Mojo%3A%3ADOM58#content)), 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](https://metacpan.org/pod/Mojo%3A%3ADOM58#find)).

    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](https://metacpan.org/pod/Mojo%3A%3ADOM58#tag)), 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](https://metacpan.org/pod/Mojo%3A%3ADOM58#text)), 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](https://metacpan.org/pod/Mojo%3A%3ADOM58#val)), 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](https://metacpan.org/pod/Test2%3A%3ATools%3A%3AHTTP)

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

- [Test2::MojoX](https://metacpan.org/pod/Test2%3A%3AMojoX)

    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.