diff --git a/README.md b/README.md index 8134736..ca3b7db 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,107 @@ composer require sinnbeck/laravel-dom-assertions --dev ## Usage +### Testing regular dom +When calling a route in a test you might want to make sure that the view contains certain elements. To test this you can use the `->assertElement()` method on the test response. +The following will ensure that there is a body tag in the parsed response. Be aware that this package assumes a proper html structure and will wrap your html in a html and body tag if one is missing! +```php +$this->get('/some-route') + ->assertElement(); +``` +In case you want to get a specific element on the page, you can supply a css selector as the first argument to get a specific one. +```php +$this->get('/some-route') + ->assertElement('#nav'); +``` +The second argument of `->assertElement()` is a closure that receives an instance of `\Sinnbeck\DomAssertions\Asserts\ElementAssert`. This allows you to assert things about the element itself. Here we are asserting that the element is a `div`. + +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->is('div'); + }); +``` +Just like with forms you can assert that certain attributes are present +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->has('x-data', '{foo: 1}'); + }); +``` +You can also ensure that certain children exist. +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->contains('div'); + }); +``` +If you need to be more specific you can use a css selector. +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->contains('div:nth-of-type(3)'); + }); +``` +You can also check that the child element has certain attributes. +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->contains('li.list-item', [ + 'x-data' => 'foobar' + ]); + }); +``` +or ensure that certain children does not exist +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->doesntContain('li.list-item', [ + 'x-data' => 'foobar' + ]); + }); +``` +You can also find a certain element and do assertions on it. Be aware that it will only check the first matching element. +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->find('li.list-item'); + }); +``` +You can add a closure as the second argument which receives an instance of `\Sinnbeck\DomAssertions\Asserts\ElementAssert`. +```php +$this->get('/some-route') + ->assertElement('#overview', function (ElementAssert $assert) { + $assert->find('li.nth-of-type(3)', function (ElementAssert $element) { + $this->is('li'); + }) + }); +``` + +This means that you can infinitely assert down the dom structure. +```php +$this->get('/some-route') + ->assertElement(function (ElementAssert $element) { + $element->find('div', function (ElementAssert $element) { + $element->is('div'); + $element->contains('p', function (ElementAssert $element) { + $element->is('p'); + $element->contains('#label', function (ElementAssert $element) { + $element->is('span'); + }); + }); + $element->contains('p:nth-of-type(2)', function (ElementAssert $element) { + $element->is('p'); + $element->contains('.sub-header', function (ElementAssert $element) { + $element->is('h4'); + }); + }); + }); + }); +``` + ### Testing forms -When calling a route in a test you might want to make sure that the view contains a form. To test this you can use the `->assertForm()` method on the test response. +Testing forms allows using all the dom asserts from above, but has a few special helpers to help test for forms. +Instead of using `->assertElement()` we will use `->assertForm()` method on the test response. ```php $this->get('/some-route') ->assertForm(); @@ -199,104 +298,7 @@ $this->get('/some-route') }); }); ``` -### Testing regular dom -The testing of generic html elements works a lot like forms. -When calling a route in a test you might want to make sure that the view contains certain elements. To test this you can use the `->assertElement()` method on the test response. -The following will ensure that there is a body tag in the parsed response. Be aware that this package assumes a proper html structure and will wrap your html in a html and body tag if one is missing! -```php -$this->get('/some-route') - ->assertElement(); -``` -In case you want to get a specific element on the page, you can supply a css selector as the first argument to get a specific one. -```php -$this->get('/some-route') - ->assertElement('#nav'); -``` -The second argument of `->assertElement()` is a closure that receives an instance of `\Sinnbeck\DomAssertions\Asserts\ElementAssert`. This allows you to assert things about the element itself. Here we are asserting that the element is a `div`. -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->is('div'); - }); -``` -Just like with forms you can assert that certain attributes are present -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->has('x-data', '{foo: 1}'); - }); -``` -You can also ensure that certain children exist. -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->contains('div'); - }); -``` -If you need to be more specific you can use a css selector. -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->contains('div:nth-of-type(3)'); - }); -``` -You can also check that the child element has certain attributes. -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->contains('li.list-item', [ - 'x-data' => 'foobar' - ]); - }); -``` -or ensure that certain children does not exist -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->doesntContain('li.list-item', [ - 'x-data' => 'foobar' - ]); - }); -``` -You can also find a certain element and do assertions on it. Be aware that it will only check the first matching element. -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->find('li.list-item'); - }); -``` -You can add a closure as the second argument which receives an instance of `\Sinnbeck\DomAssertions\Asserts\ElementAssert`. -```php -$this->get('/some-route') - ->assertElement('#overview', function (ElementAssert $assert) { - $assert->find('li.nth-of-type(3)', function (ElementAssert $element) { - $this->is('li'); - }) - }); -``` - -This means that you can infinitely assert down the dom structure. -```php -$this->get('/some-route') - ->assertElement(function (ElementAssert $element) { - $element->find('div', function (ElementAssert $element) { - $element->is('div'); - $element->contains('p', function (ElementAssert $element) { - $element->is('p'); - $element->contains('#label', function (ElementAssert $element) { - $element->is('span'); - }); - }); - $element->contains('p:nth-of-type(2)', function (ElementAssert $element) { - $element->is('p'); - $element->contains('.sub-header', function (ElementAssert $element) { - $element->is('h4'); - }); - }); - }); - }); -``` ## Testing this package ```bash diff --git a/src/Asserts/FormAssert.php b/src/Asserts/FormAssert.php index 2a2f0bc..ff6c483 100644 --- a/src/Asserts/FormAssert.php +++ b/src/Asserts/FormAssert.php @@ -102,7 +102,7 @@ public function containsSelect($selector = 'select', $callback = null): static $selector = 'select'; } - if (! $select = $this->makeScopedParser('form')->query($selector)) { + if (! $select = $this->getParser()->query($selector)) { Assert::fail(sprintf('No select found for selector: %s', $selector)); } diff --git a/src/Asserts/SelectAssert.php b/src/Asserts/SelectAssert.php index c72f140..0a272cf 100644 --- a/src/Asserts/SelectAssert.php +++ b/src/Asserts/SelectAssert.php @@ -26,12 +26,12 @@ public function __construct(string $html, $root) public function containsOption(mixed $attributes): self { - $this->gatherAttributesWithText('option'); - if (is_array($attributes)) { - $this->contains('option', $attributes); + return $this->contains('option', $attributes); } + $this->gatherAttributes('option'); + if (is_callable($attributes)) { tap( new OptionAssert( @@ -52,12 +52,13 @@ public function containsOptions(...$attributes): self return $this; } - public function hasValue($value) + public function hasValue($value): self { Assert::assertNotNull( - $option = $this->makeScopedParser()->query('option[selected="selected"]'), + $option = $this->getParser()->query('option[selected="selected"]'), 'No options are selected!' ); + Assert::assertEquals( $value, $option->getAttribute('value') @@ -66,14 +67,15 @@ public function hasValue($value) return $this; } - public function hasValues(array $values) + public function hasValues(array $values): self { Assert::assertNotNull( - $this->makeScopedParser()->query('option[selected="selected"]'), + $this->getParser()->query('option[selected="selected"]'), 'No options are selected!' ); - foreach ($this->makeScopedParser()->queryAll('option[selected="selected"]') as $option) { + $selected = []; + foreach ($this->getParser()->queryAll('option[selected="selected"]') as $option) { $selected[] = $this->getAttributeFor($option, 'value'); } @@ -82,5 +84,7 @@ public function hasValues(array $values) $selected, sprintf('Selected values does not match') ); + + return $this; } } diff --git a/src/Asserts/Traits/CanGatherAttributes.php b/src/Asserts/Traits/CanGatherAttributes.php index 0be212c..06db768 100644 --- a/src/Asserts/Traits/CanGatherAttributes.php +++ b/src/Asserts/Traits/CanGatherAttributes.php @@ -6,36 +6,15 @@ trait CanGatherAttributes { use InteractsWithParser; - public function gatherAttributesWithText($type) + public function gatherAttributes($type): void { if (isset($this->attributes[$type])) { return; } $this->attributes[$type] = []; - $elements = $this->parser->getElementsByType($type); - /** @var \DOMElement $element */ - foreach ($elements as $element) { - $attributes = []; - foreach ($element->attributes as $attribute) { - $attributes[$attribute->nodeName] = $attribute->value; - } - $this->attributes[$type][] = $attributes + [ - 'text' => trim($element->nodeValue), - ]; - } - } - - public function gatherAttributes($type) - { - if (isset($this->attributes[$type])) { - return; - } - - $this->attributes[$type] = []; - - $elements = $this->makeScopedParser()->queryAll($type); + $elements = $this->getParser()->queryAll($type); $extra = []; /** @var \DOMElement $element */ diff --git a/src/Asserts/Traits/HasElementAsserts.php b/src/Asserts/Traits/HasElementAsserts.php index a2a702e..ef47c86 100644 --- a/src/Asserts/Traits/HasElementAsserts.php +++ b/src/Asserts/Traits/HasElementAsserts.php @@ -12,17 +12,17 @@ trait HasElementAsserts public function __call(string $method, array $arguments) { if (Str::startsWith($method, 'has')) { - $property = Str::of($method)->after('has')->snake()->slug('-'); + $property = Str::of($method)->after('has')->snake()->slug(); $this->has($property, $arguments[0]); } if (Str::startsWith($method, 'is')) { - $property = Str::of($method)->after('is')->snake()->slug('-'); + $property = Str::of($method)->after('is')->snake()->slug(); $this->is($property); } if (Str::startsWith($method, 'find')) { - $property = Str::of($method)->after('find')->snake()->slug('-'); + $property = Str::of($method)->after('find')->snake()->slug(); $this->find($property, $arguments[0]); } @@ -53,7 +53,7 @@ public function has(string $attribute, mixed $value): self public function find(string $selector, $callback = null): self { Assert::assertNotNull( - $element = $this->parser->query($selector), + $element = $this->getParser()->query($selector), sprintf('Could not find any matching element for selector "%s"', $selector) ); @@ -68,7 +68,7 @@ public function find(string $selector, $callback = null): self public function contains(string $elementName, array $attributes = []): self { Assert::assertNotNull( - $this->parser->query($elementName), + $this->getParser()->query($elementName), sprintf('Could not find any matching element of type "%s"', $elementName) ); @@ -93,7 +93,7 @@ public function doesntContain(string $elementName, array $attributes = []): self { if (! $attributes) { Assert::assertNull( - $this->parser->query($elementName), + $this->getParser()->query($elementName), sprintf('Found a matching element of type "%s"', $elementName) ); @@ -117,14 +117,14 @@ public function is(string $type): self { PHPUnit::assertEquals( $type, - $this->parser->getType(), + $this->getParser()->getType(), sprintf('Element is not of type "%s"', $type) ); return $this; } - private function compareAttributesArrays($attributes, $foundAttributes) + private function compareAttributesArrays($attributes, $foundAttributes): bool { return ! array_diff($attributes, $foundAttributes); } diff --git a/src/Asserts/Traits/InteractsWithParser.php b/src/Asserts/Traits/InteractsWithParser.php index bd5bb62..b92e2ae 100644 --- a/src/Asserts/Traits/InteractsWithParser.php +++ b/src/Asserts/Traits/InteractsWithParser.php @@ -6,31 +6,23 @@ trait InteractsWithParser { - protected function makeScopedParser($root = null): DomParser + protected function getParser(): DomParser { - $clone = $this->parser->cloneFromRoot(); - - if (is_string($root)) { - $clone->setRootFromString($root); - } elseif ($root instanceof \DOMElement) { - $clone->setRoot($root); - } - - return $clone; + return $this->parser; } protected function getContent() { - return $this->parser->getContent(); + return $this->getParser()->getContent(); } protected function getAttribute(string $attribute) { - return $this->parser->getAttributeForRoot($attribute); + return $this->getParser()->getAttributeForRoot($attribute); } protected function getAttributeFor($for, string $attribute) { - return $this->parser->getAttributeFor($for, $attribute); + return $this->getParser()->getAttributeFor($for, $attribute); } } diff --git a/src/DomParser.php b/src/DomParser.php index 910135f..71624a1 100644 --- a/src/DomParser.php +++ b/src/DomParser.php @@ -98,15 +98,14 @@ public function getType() public function query($selector): DOMNode|null { - $converter = new CssSelectorConverter(); - - return (new \DOMXPath($this->getRoot()->ownerDocument))->query($converter->toXpath($selector))->item(0); + return $this->queryAll($selector)->item(0); } public function queryAll(string $selector): \DOMNodeList { $converter = new CssSelectorConverter(); + $parser = $this->cloneFromRoot(); - return (new \DOMXPath($this->getRoot()->ownerDocument))->query($converter->toXpath($selector)); + return (new \DOMXPath($parser->getRoot()->ownerDocument))->query($converter->toXpath($selector)); } } diff --git a/tests/DomParserTest.php b/tests/DomParserTest.php index 42bfd66..b10dfcb 100644 --- a/tests/DomParserTest.php +++ b/tests/DomParserTest.php @@ -56,3 +56,22 @@ $this->assertEquals($parser->getElementOfType('input')->nodeName, 'input'); }); + +it('can query a scope', function () { + $html = <<<'HTML' +
+
+ +
+ +
+HTML; + + $parser = DomParser::new($html); + $ul = $parser->getElementOfType('ul'); + $parser->setRoot($ul); + + $this->assertEquals($parser->query('.foo')->nodeName, 'li'); +}); diff --git a/tests/DomTest.php b/tests/DomTest.php index 9af40cb..ec477b6 100644 --- a/tests/DomTest.php +++ b/tests/DomTest.php @@ -111,3 +111,13 @@ }); }); }); + +it('can find a nested element and ensure doesnt contain', function () { + $this->get('nesting') + ->assertElement(function (ElementAssert $element) { + $element->findDiv(function (ElementAssert $element) { + $element->is('div'); + $element->doesntContain('nav'); + }); + }); +}); diff --git a/tests/FormTest.php b/tests/FormTest.php index 377c56d..c072fb3 100644 --- a/tests/FormTest.php +++ b/tests/FormTest.php @@ -133,6 +133,7 @@ 'x-data' => 'none', 'value' => 'none', 'text' => 'None', + 'selected' => 'selected', ]) ->containsOptions( [