<?php

declare(strict_types=1);

namespace Laminas\Test\PHPUnit\Controller;

use ArrayIterator;
use Laminas\Http\Header\HeaderInterface;
use Laminas\Test\PHPUnit\Constraint\HasRedirectConstraint;
use Laminas\Test\PHPUnit\Constraint\IsRedirectedRouteNameConstraint;
use PHPUnit\Framework\ExpectationFailedException;
use Symfony\Component\DomCrawler\Crawler;

use function count;
use function implode;
use function preg_match;
use function sprintf;

abstract class AbstractHttpControllerTestCase extends AbstractControllerTestCase
{
    /**
     * HTTP controller must not use the console request
     *
     * @var bool
     */
    protected $useConsoleRequest = false;

    /**
     * XPath namespaces
     *
     * @var array<string,string>
     */
    protected $xpathNamespaces = [];

    /**
     * Get response header by key
     *
     * @param  string $header
     * @return HeaderInterface|false
     */
    protected function getResponseHeader($header)
    {
        $response = $this->getResponse();
        $headers  = $response->getHeaders();
        return $headers->get($header, false);
    }

    /**
     * Assert response has the given reason phrase
     *
     * @param string $phrase
     * @return void
     */
    public function assertResponseReasonPhrase($phrase)
    {
        $this->assertEquals($phrase, $this->getResponse()->getReasonPhrase());
    }

    /**
     * Assert response header exists
     *
     * @param string $header
     * @return void
     */
    public function assertHasResponseHeader($header)
    {
        $responseHeader = $this->getResponseHeader($header);
        if (false === $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header "%s" found',
                $header
            )));
        }
        $this->assertNotEquals(false, $responseHeader);
    }

    /**
     * Assert response header does not exist
     *
     * @param string $header
     * @return void
     */
    public function assertNotHasResponseHeader($header)
    {
        $responseHeader = $this->getResponseHeader($header);
        if (false !== $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header "%s" WAS NOT found',
                $header
            )));
        }
        $this->assertFalse($responseHeader);
    }

    /**
     * Assert response header exists and contains the given string
     *
     * @param string $header
     * @param string $match
     * @return void
     */
    public function assertResponseHeaderContains($header, $match)
    {
        $responseHeader = $this->getResponseHeader($header);
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header, header "%s" doesn\'t exist',
                $header
            )));
        }

        if (! $responseHeader instanceof ArrayIterator) {
            $responseHeader = [$responseHeader];
        }

        $headerMatched = false;

        foreach ($responseHeader as $currentHeader) {
            if ($match === $currentHeader->getFieldValue()) {
                $headerMatched = true;
                break;
            }
        }

        if (! $headerMatched) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header "%s" exists and contains "%s", actual content is "%s"',
                $header,
                $match,
                $currentHeader->getFieldValue()
            )));
        }

        $this->assertEquals($match, $currentHeader->getFieldValue());
    }

    /**
     * Assert response header exists and contains the given string
     *
     * @param string $header
     * @param string $match
     * @return void
     */
    public function assertNotResponseHeaderContains($header, $match)
    {
        $responseHeader = $this->getResponseHeader($header);
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header, header "%s" doesn\'t exist',
                $header
            )));
        }

        if (! $responseHeader instanceof ArrayIterator) {
            $responseHeader = [$responseHeader];
        }

        foreach ($responseHeader as $currentHeader) {
            if ($match === $currentHeader->getFieldValue()) {
                throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                    'Failed asserting response header "%s" DOES NOT CONTAIN "%s"',
                    $header,
                    $match
                )));
            }
        }

        $this->assertNotEquals($match, $currentHeader->getFieldValue());
    }

    /**
     * Assert response header exists and matches the given pattern
     *
     * @param string $header
     * @param string $pattern
     * @return void
     */
    public function assertResponseHeaderRegex($header, $pattern)
    {
        $responseHeader = $this->getResponseHeader($header);
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header, header "%s" doesn\'t exist',
                $header
            )));
        }

        if (! $responseHeader instanceof ArrayIterator) {
            $responseHeader = [$responseHeader];
        }

        $headerMatched = false;

        foreach ($responseHeader as $currentHeader) {
            $headerMatched = (bool) preg_match($pattern, $currentHeader->getFieldValue());

            if ($headerMatched) {
                break;
            }
        }

        if (! $headerMatched) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header "%s" exists and matches regex "%s", actual content is "%s"',
                $header,
                $pattern,
                $currentHeader->getFieldValue()
            )));
        }

        $this->assertTrue($headerMatched);
    }

    /**
     * Assert response header does not exist and/or does not match the given regex
     *
     * @param string $header
     * @param string $pattern
     * @return void
     */
    public function assertNotResponseHeaderRegex($header, $pattern)
    {
        $responseHeader = $this->getResponseHeader($header);
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response header, header "%s" doesn\'t exist',
                $header
            )));
        }

        if (! $responseHeader instanceof ArrayIterator) {
            $responseHeader = [$responseHeader];
        }

        $headerMatched = false;

        foreach ($responseHeader as $currentHeader) {
            $headerMatched = (bool) preg_match($pattern, $currentHeader->getFieldValue());

            if ($headerMatched) {
                throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                    'Failed asserting response header "%s" DOES NOT MATCH regex "%s"',
                    $header,
                    $pattern
                )));
            }
        }

        $this->assertFalse($headerMatched);
    }

    /**
     * Assert that response is a redirect
     *
     * @return void
     */
    public function assertRedirect()
    {
        $responseHeader = $this->getResponseHeader('Location');
        if (false === $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(
                'Failed asserting response is a redirect'
            ));
        }
        $this->assertNotEquals(false, $responseHeader);
    }

    /**
     * Assert that response is NOT a redirect
     *
     * @return void
     */
    public function assertNotRedirect()
    {
        $responseHeader = $this->getResponseHeader('Location');
        if (false !== $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response is NOT a redirect, actual redirection is "%s"',
                $responseHeader->getFieldValue()
            )));
        }
        $this->assertFalse($responseHeader);
    }

    /**
     * Assert that response redirects to given URL
     *
     * @param string $url
     * @return void
     */
    public function assertRedirectTo($url)
    {
        $responseHeader = $this->getResponseHeader('Location');
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(
                'Failed asserting response is a redirect'
            ));
        }
        if ($url !== $responseHeader->getFieldValue()) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response redirects to "%s", actual redirection is "%s"',
                $url,
                $responseHeader->getFieldValue()
            )));
        }
        $this->assertEquals($url, $responseHeader->getFieldValue());
    }

    /**
     * Assert that response does not redirect to given URL
     *
     * @param string $url
     * @return void
     */
    public function assertNotRedirectTo($url)
    {
        $responseHeader = $this->getResponseHeader('Location');
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(
                'Failed asserting response is a redirect'
            ));
        }
        if ($url === $responseHeader->getFieldValue()) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response redirects to "%s"',
                $url
            )));
        }
        $this->assertNotEquals($url, $responseHeader->getFieldValue());
    }

    /**
     * Assert that response redirects to given route
     */
    final public function assertRedirectToRoute(string $route): void
    {
        self::assertThat(
            $route,
            self::logicalAnd(
                new HasRedirectConstraint($this),
                new IsRedirectedRouteNameConstraint($this)
            )
        );
    }

    /**
     * Assert that response does not redirect to given route
     */
    final public function assertNotRedirectToRoute(string $route): void
    {
        self::assertThat(
            $route,
            self::logicalNot(
                self::logicalAnd(
                    new HasRedirectConstraint($this),
                    new IsRedirectedRouteNameConstraint($this)
                )
            )
        );
    }

    /**
     * Assert that redirect location matches pattern
     *
     * @param string $pattern
     * @return void
     */
    public function assertRedirectRegex($pattern)
    {
        $responseHeader = $this->getResponseHeader('Location');
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(
                'Failed asserting response is a redirect'
            ));
        }
        if (! preg_match($pattern, $responseHeader->getFieldValue())) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response redirects to URL MATCHING "%s", actual redirection is "%s"',
                $pattern,
                $responseHeader->getFieldValue()
            )));
        }
        $this->assertTrue((bool) preg_match($pattern, $responseHeader->getFieldValue()));
    }

    /**
     * Assert that redirect location does not match pattern
     *
     * @param string $pattern
     * @return void
     */
    public function assertNotRedirectRegex($pattern)
    {
        $responseHeader = $this->getResponseHeader('Location');
        if (! $responseHeader) {
            throw new ExpectationFailedException($this->createFailureMessage(
                'Failed asserting response is a redirect'
            ));
        }
        if (preg_match($pattern, $responseHeader->getFieldValue())) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting response DOES NOT redirect to URL MATCHING "%s"',
                $pattern
            )));
        }
        $this->assertFalse((bool) preg_match($pattern, $responseHeader->getFieldValue()));
    }

    /**
     * Register XPath namespaces
     *
     * @param array<string,string> $xpathNamespaces
     * @return void
     */
    public function registerXpathNamespaces(array $xpathNamespaces)
    {
        $this->xpathNamespaces = $xpathNamespaces;
    }

    /**
     * Execute a DOM/XPath query
     *
     * @param  string $path
     * @param  bool $useXpath
     * @return Crawler
     */
    private function query($path, $useXpath = false)
    {
        $response = $this->getResponse();
        $document = new Crawler($response->getContent());

        if ($useXpath) {
            foreach ($this->xpathNamespaces as $prefix => $namespace) {
                $document->registerNamespace($prefix, $namespace);
            }
        }

        return $useXpath ? $document->filterXPath($path) : $document->filter($path);
    }

    /**
     * Execute a xpath query
     *
     * @param string $path
     */
    private function xpathQuery($path): Crawler
    {
        return $this->query($path, true);
    }

    /**
     * Count the dom query executed
     *
     * @param  string $path
     * @return int
     */
    private function queryCount($path)
    {
        return count($this->query($path, false));
    }

    /**
     * Count the dom query executed
     *
     * @param  string $path
     * @return int
     */
    private function xpathQueryCount($path)
    {
        return $this->xpathQuery($path)->count();
    }

    /**
     * @param string $path
     * @param bool $useXpath
     */
    private function queryCountOrxpathQueryCount($path, $useXpath = false): int
    {
        if ($useXpath) {
            return $this->xpathQueryCount($path);
        }

        return $this->queryCount($path);
    }

    /**
     * Assert against DOM/XPath selection
     *
     * @param string $path
     * @param bool $useXpath
     */
    private function queryAssertion($path, $useXpath = false): void
    {
        $match = $this->queryCountOrxpathQueryCount($path, $useXpath);
        if (! $match > 0) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s EXISTS',
                $path
            )));
        }
        $this->assertTrue($match > 0);
    }

    /**
     * Assert against DOM selection
     *
     * @param string $path CSS selector path
     * @return void
     */
    public function assertQuery($path)
    {
        $this->queryAssertion($path, false);
    }

    /**
     * Assert against XPath selection
     *
     * @param string $path XPath path
     * @return void
     */
    public function assertXpathQuery($path)
    {
        $this->queryAssertion($path, true);
    }

    /**
     * Assert against DOM/XPath selection
     *
     * @param string $path CSS selector path
     * @param bool $useXpath
     */
    private function notQueryAssertion($path, $useXpath = false): void
    {
        $match = $this->queryCountOrxpathQueryCount($path, $useXpath);
        if ($match !== 0) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s DOES NOT EXIST',
                $path
            )));
        }
        $this->assertEquals(0, $match);
    }

    /**
     * Assert against DOM selection
     *
     * @param string $path CSS selector path
     * @return void
     */
    public function assertNotQuery($path)
    {
        $this->notQueryAssertion($path, false);
    }

    /**
     * Assert against XPath selection
     *
     * @param string $path XPath path
     * @return void
     */
    public function assertNotXpathQuery($path)
    {
        $this->notQueryAssertion($path, true);
    }

    /**
     * Assert against DOM/XPath selection; should contain exact number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Number of nodes that should match
     * @param bool $useXpath
     */
    private function queryCountAssertion($path, $count, $useXpath = false): void
    {
        $match = $this->queryCountOrxpathQueryCount($path, $useXpath);
        if ($match !== $count) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s OCCURS EXACTLY %d times, actually occurs %d times',
                $path,
                $count,
                $match
            )));
        }
        $this->assertEquals($match, $count);
    }

    /**
     * Assert against DOM selection; should contain exact number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Number of nodes that should match
     * @return void
     */
    public function assertQueryCount($path, $count)
    {
        $this->queryCountAssertion($path, $count, false);
    }

    /**
     * Assert against XPath selection; should contain exact number of nodes
     *
     * @param string $path XPath path
     * @param int $count Number of nodes that should match
     * @return void
     */
    public function assertXpathQueryCount($path, $count)
    {
        $this->queryCountAssertion($path, $count, true);
    }

    /**
     * Assert against DOM/XPath selection; should NOT contain exact number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Number of nodes that should NOT match
     * @param bool $useXpath
     */
    private function notQueryCountAssertion($path, $count, $useXpath = false): void
    {
        $match = $this->queryCountOrxpathQueryCount($path, $useXpath);
        if ($match === $count) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s DOES NOT OCCUR EXACTLY %d times',
                $path,
                $count
            )));
        }
        $this->assertNotEquals($match, $count);
    }

    /**
     * Assert against DOM selection; should NOT contain exact number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Number of nodes that should NOT match
     * @return void
     */
    public function assertNotQueryCount($path, $count)
    {
        $this->notQueryCountAssertion($path, $count, false);
    }

    /**
     * Assert against XPath selection; should NOT contain exact number of nodes
     *
     * @param string $path XPath path
     * @param int $count Number of nodes that should NOT match
     * @return void
     */
    public function assertNotXpathQueryCount($path, $count)
    {
        $this->notQueryCountAssertion($path, $count, true);
    }

    /**
     * Assert against DOM/XPath selection; should contain at least this number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Minimum number of nodes that should match
     * @param bool $useXpath
     */
    private function queryCountMinAssertion($path, $count, $useXpath = false): void
    {
        $match = $this->queryCountOrxpathQueryCount($path, $useXpath);
        if ($match < $count) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s OCCURS AT LEAST %d times, actually occurs %d times',
                $path,
                $count,
                $match
            )));
        }
        $this->assertTrue($match >= $count);
    }

    /**
     * Assert against DOM selection; should contain at least this number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Minimum number of nodes that should match
     * @return void
     */
    public function assertQueryCountMin($path, $count)
    {
        $this->queryCountMinAssertion($path, $count, false);
    }

    /**
     * Assert against XPath selection; should contain at least this number of nodes
     *
     * @param string $path XPath path
     * @param int $count Minimum number of nodes that should match
     * @return void
     */
    public function assertXpathQueryCountMin($path, $count)
    {
        $this->queryCountMinAssertion($path, $count, true);
    }

    /**
     * Assert against DOM/XPath selection; should contain no more than this number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Maximum number of nodes that should match
     * @param bool $useXpath
     */
    private function queryCountMaxAssertion($path, $count, $useXpath = false): void
    {
        $match = $this->queryCountOrxpathQueryCount($path, $useXpath);
        if ($match > $count) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s OCCURS AT MOST %d times, actually occurs %d times',
                $path,
                $count,
                $match
            )));
        }
        $this->assertTrue($match <= $count);
    }

    /**
     * Assert against DOM selection; should contain no more than this number of nodes
     *
     * @param string $path CSS selector path
     * @param int $count Maximum number of nodes that should match
     * @return void
     */
    public function assertQueryCountMax($path, $count)
    {
        $this->queryCountMaxAssertion($path, $count, false);
    }

    /**
     * Assert against XPath selection; should contain no more than this number of nodes
     *
     * @param string $path XPath path
     * @param int $count Maximum number of nodes that should match
     * @return void
     */
    public function assertXpathQueryCountMax($path, $count)
    {
        $this->queryCountMaxAssertion($path, $count, true);
    }

    /**
     * Assert against DOM/XPath selection; node should contain content
     *
     * @param string $path CSS selector path
     * @param string $match content that should be contained in matched nodes
     * @param bool $useXpath
     */
    private function queryContentContainsAssertion($path, $match, $useXpath = false): void
    {
        $result = $this->query($path, $useXpath);

        if ($result->count() === 0) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s EXISTS',
                $path
            )));
        }

        $nodeValues = [];

        foreach ($result as $node) {
            if ($node->nodeValue === $match) {
                $this->assertEquals($match, $node->nodeValue);
                return;
            }

            $nodeValues[] = $node->nodeValue;
        }

        throw new ExpectationFailedException($this->createFailureMessage(sprintf(
            'Failed asserting node denoted by %s CONTAINS content "%s", Contents: [%s]',
            $path,
            $match,
            implode(',', $nodeValues)
        )));
    }

    /**
     * Assert against DOM selection; node should contain content
     *
     * @param string $path CSS selector path
     * @param string $match content that should be contained in matched nodes
     * @return void
     */
    public function assertQueryContentContains($path, $match)
    {
        $this->queryContentContainsAssertion($path, $match, false);
    }

    /**
     * Assert against XPath selection; node should contain content
     *
     * @param string $path XPath path
     * @param string $match content that should be contained in matched nodes
     * @return void
     */
    public function assertXpathQueryContentContains($path, $match)
    {
        $this->queryContentContainsAssertion($path, $match, true);
    }

    /**
     * Assert against DOM/XPath selection; node should NOT contain content
     *
     * @param string $path CSS selector path
     * @param string $match content that should NOT be contained in matched nodes
     * @param bool $useXpath
     */
    private function notQueryContentContainsAssertion($path, $match, $useXpath = false): void
    {
        $result = $this->query($path, $useXpath);
        if ($result->count() === 0) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s EXISTS',
                $path
            )));
        }
        foreach ($result as $node) {
            if ($node->nodeValue === $match) {
                throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                    'Failed asserting node DENOTED BY %s DOES NOT CONTAIN content "%s"',
                    $path,
                    $match
                )));
            }
        }
        $currentValue = $node->nodeValue;
        $this->assertNotEquals($currentValue, $match);
    }

    /**
     * Assert against DOM selection; node should NOT contain content
     *
     * @param string $path CSS selector path
     * @param string $match content that should NOT be contained in matched nodes
     * @return void
     */
    public function assertNotQueryContentContains($path, $match)
    {
        $this->notQueryContentContainsAssertion($path, $match, false);
    }

    /**
     * Assert against XPath selection; node should NOT contain content
     *
     * @param string $path XPath path
     * @param string $match content that should NOT be contained in matched nodes
     * @return void
     */
    public function assertNotXpathQueryContentContains($path, $match)
    {
        $this->notQueryContentContainsAssertion($path, $match, true);
    }

    /**
     * Assert against DOM/XPath selection; node should match content
     *
     * @param string $path CSS selector path
     * @param string $pattern Pattern that should be contained in matched nodes
     * @param bool $useXpath
     */
    private function queryContentRegexAssertion($path, $pattern, $useXpath = false): void
    {
        $result = $this->query($path, $useXpath);
        if ($result->count() === 0) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s EXISTS',
                $path
            )));
        }

        $found      = false;
        $nodeValues = [];

        foreach ($result as $node) {
            $nodeValues[] = $node->nodeValue;
            if (preg_match($pattern, $node->nodeValue)) {
                $found = true;
                break;
            }
        }

        if (! $found) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node denoted by %s CONTAINS content MATCHING "%s", actual content is "%s"',
                $path,
                $pattern,
                implode('', $nodeValues)
            )));
        }

        $this->assertTrue($found);
    }

    /**
     * Assert against DOM selection; node should match content
     *
     * @param string $path CSS selector path
     * @param string $pattern Pattern that should be contained in matched nodes
     * @return void
     */
    public function assertQueryContentRegex($path, $pattern)
    {
        $this->queryContentRegexAssertion($path, $pattern, false);
    }

    /**
     * Assert against XPath selection; node should match content
     *
     * @param string $path XPath path
     * @param string $pattern Pattern that should be contained in matched nodes
     * @return void
     */
    public function assertXpathQueryContentRegex($path, $pattern)
    {
        $this->queryContentRegexAssertion($path, $pattern, true);
    }

    /**
     * Assert against DOM/XPath selection; node should NOT match content
     *
     * @param string $path CSS selector path
     * @param string $pattern pattern that should NOT be contained in matched nodes
     * @param bool $useXpath
     */
    private function notQueryContentRegexAssertion($path, $pattern, $useXpath = false): void
    {
        $result = $this->query($path, $useXpath);
        if ($result->count() === 0) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s EXISTS',
                $path
            )));
        }
        $node      = $result->getNode(0);
        $nodeValue = $node ? $node->nodeValue : null;

        if ($nodeValue === null || preg_match($pattern, $nodeValue)) {
            throw new ExpectationFailedException($this->createFailureMessage(sprintf(
                'Failed asserting node DENOTED BY %s DOES NOT CONTAIN content MATCHING "%s"',
                $path,
                $pattern
            )));
        }
        $this->assertFalse((bool) preg_match($pattern, $nodeValue));
    }

    /**
     * Assert against DOM selection; node should NOT match content
     *
     * @param string $path CSS selector path
     * @param string $pattern pattern that should NOT be contained in matched nodes
     * @return void
     */
    public function assertNotQueryContentRegex($path, $pattern)
    {
        $this->notQueryContentRegexAssertion($path, $pattern, false);
    }

    /**
     * Assert against XPath selection; node should NOT match content
     *
     * @param string $path XPath path
     * @param string $pattern pattern that should NOT be contained in matched nodes
     * @return void
     */
    public function assertNotXpathQueryContentRegex($path, $pattern)
    {
        $this->notQueryContentRegexAssertion($path, $pattern, true);
    }
}