diff --git a/src/Illuminate/Testing/Constraints/SeeInHtml.php b/src/Illuminate/Testing/Constraints/SeeInHtml.php new file mode 100644 index 000000000000..24fd0859b35d --- /dev/null +++ b/src/Illuminate/Testing/Constraints/SeeInHtml.php @@ -0,0 +1,138 @@ +content = $content; + $this->ordered = $ordered; + $this->negate = $negate; + } + + /** + * Determine if the rule passes validation. + * + * @param array $values + * @return bool + */ + public function matches($values): bool + { + $normalizedContent = $this->normalize($this->content); + + $position = 0; + + foreach ($values as $value) { + if (empty($value)) { + continue; + } + + $normalizedValue = $this->normalize($value); + + $valuePosition = mb_strpos($normalizedContent, $normalizedValue, $position); + + if ($this->negate) { + if ($valuePosition !== false) { + $this->failedValue = $value; + + return false; + } + + continue; + } + + if ($valuePosition === false || $valuePosition < $position) { + $this->failedValue = $value; + + return false; + } + + if ($this->ordered) { + $position = $valuePosition + mb_strlen($normalizedValue); + } + } + + return true; + } + + /** + * Get the description of the failure. + * + * @param array $values + * @return string + */ + public function failureDescription($values): string + { + if ($this->negate) { + return sprintf( + '\'%s\' does not contain "%s".', + $this->content, + $this->failedValue + ); + } + + return sprintf( + '\'%s\' contains "%s"%s', + $this->content, + $this->failedValue, + $this->ordered ? ' in specified order.' : '.' + ); + } + + /** + * Get a string representation of the object. + * + * @return string + */ + public function toString(): string + { + return (new ReflectionClass($this))->name; + } + + protected function normalize(string $value): ?string + { + $value = strip_tags($value); + $value = html_entity_decode($value, ENT_QUOTES, 'UTF-8'); + $value = trim($value); + $value = preg_replace('/\s+/', ' ', $value); + + return $value; + } +} diff --git a/src/Illuminate/Testing/Constraints/SeeInOrder.php b/src/Illuminate/Testing/Constraints/SeeInOrder.php index 08d30ab82fe6..88d337c3b231 100644 --- a/src/Illuminate/Testing/Constraints/SeeInOrder.php +++ b/src/Illuminate/Testing/Constraints/SeeInOrder.php @@ -73,7 +73,7 @@ public function matches($values): bool public function failureDescription($values): string { return sprintf( - 'Failed asserting that \'%s\' contains "%s" in specified order.', + '\'%s\' contains "%s" in specified order.', $this->content, $this->failedValue ); diff --git a/src/Illuminate/Testing/TestComponent.php b/src/Illuminate/Testing/TestComponent.php index 43138ee48fb7..a263fa6dcd25 100644 --- a/src/Illuminate/Testing/TestComponent.php +++ b/src/Illuminate/Testing/TestComponent.php @@ -5,6 +5,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Traits\Macroable; use Illuminate\Testing\Assert as PHPUnit; +use Illuminate\Testing\Constraints\SeeInHtml; use Illuminate\Testing\Constraints\SeeInOrder; use Stringable; @@ -112,11 +113,7 @@ public function assertSeeText($value, $escape = true) $values = $escape ? array_map(e(...), $value) : $value; - $rendered = strip_tags($this->rendered); - - foreach ($values as $value) { - PHPUnit::assertStringContainsString((string) $value, $rendered); - } + PHPUnit::assertThat($values, new SeeInHtml($this->rendered)); return $this; } @@ -132,7 +129,7 @@ public function assertSeeTextInOrder(array $values, $escape = true) { $values = $escape ? array_map(e(...), $values) : $values; - PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->rendered))); + PHPUnit::assertThat($values, new SeeInHtml($this->rendered, true)); return $this; } diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 783d0d2263ab..13a14fd9035d 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -19,6 +19,7 @@ use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use Illuminate\Support\ViewErrorBag; +use Illuminate\Testing\Constraints\SeeInHtml; use Illuminate\Testing\Constraints\SeeInOrder; use Illuminate\Testing\Fluent\AssertableJson; use Illuminate\Testing\TestResponseAssert as PHPUnit; @@ -764,11 +765,7 @@ public function assertSeeText($value, $escape = true) $values = $escape ? array_map(e(...), $value) : $value; - $content = strip_tags($this->getContent()); - - foreach ($values as $value) { - PHPUnit::withResponse($this)->assertStringContainsString((string) $value, $content); - } + PHPUnit::withResponse($this)->assertThat($values, new SeeInHtml($this->getContent())); return $this; } @@ -784,7 +781,7 @@ public function assertSeeTextInOrder(array $values, $escape = true) { $values = $escape ? array_map(e(...), $values) : $values; - PHPUnit::withResponse($this)->assertThat($values, new SeeInOrder(strip_tags($this->getContent()))); + PHPUnit::withResponse($this)->assertThat($values, new SeeInHtml($this->getContent(), true)); return $this; } @@ -833,11 +830,7 @@ public function assertDontSeeText($value, $escape = true) $values = $escape ? array_map(e(...), $value) : $value; - $content = strip_tags($this->getContent()); - - foreach ($values as $value) { - PHPUnit::withResponse($this)->assertStringNotContainsString((string) $value, $content); - } + PHPUnit::withResponse($this)->assertThat($values, new SeeInHtml($this->getContent(), negate: true)); return $this; } diff --git a/src/Illuminate/Testing/TestView.php b/src/Illuminate/Testing/TestView.php index 4c3860e0a1cf..f7a6212725fa 100644 --- a/src/Illuminate/Testing/TestView.php +++ b/src/Illuminate/Testing/TestView.php @@ -8,6 +8,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Traits\Macroable; use Illuminate\Testing\Assert as PHPUnit; +use Illuminate\Testing\Constraints\SeeInHtml; use Illuminate\Testing\Constraints\SeeInOrder; use Illuminate\View\View; use Stringable; @@ -189,11 +190,7 @@ public function assertSeeText($value, $escape = true) $values = $escape ? array_map(e(...), $value) : $value; - $rendered = strip_tags($this->rendered); - - foreach ($values as $value) { - PHPUnit::assertStringContainsString((string) $value, $rendered); - } + PHPUnit::assertThat($values, new SeeInHtml($this->rendered)); return $this; } @@ -209,7 +206,7 @@ public function assertSeeTextInOrder(array $values, $escape = true) { $values = $escape ? array_map(e(...), $values) : $values; - PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->rendered))); + PHPUnit::assertThat($values, new SeeInHtml($this->rendered, true)); return $this; } @@ -258,11 +255,7 @@ public function assertDontSeeText($value, $escape = true) $values = $escape ? array_map(e(...), $value) : $value; - $rendered = strip_tags($this->rendered); - - foreach ($values as $value) { - PHPUnit::assertStringNotContainsString((string) $value, $rendered); - } + PHPUnit::assertThat($values, new SeeInHtml($this->rendered)); return $this; } diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index b7598e3b9959..3b07124fb9b8 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -594,6 +594,7 @@ public function testAssertSeeText(): void public function testAssertSeeTextCanFail(): void { $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that \'foobar\' contains "bazfoo".'); $response = $this->makeMockResponse([ 'render' => 'foobar', @@ -613,6 +614,20 @@ public function testAssertSeeTextEscaped(): void $response->assertSeeText(['php & friends', 'laravel & php']); } + public function testAssertSeeTextWhitespace(): void + { + $response = $this->makeMockResponse([ + 'render' => <<<'EOT' +

+ Hello, + laravel & php & friends +

, +EOT + ]); + + $response->assertSeeText('Hello, laravel & php & friends'); + } + public function testAssertSeeTextEscapedCanFail(): void { $this->expectException(AssertionFailedError::class); @@ -645,9 +660,24 @@ public function testAssertSeeTextInOrderEscaped(): void $response->assertSeeTextInOrder(['laravel & php', 'phpstorm > sublime']); } + public function testAssertSeeTextInOrderWhitespace(): void + { + $response = $this->makeMockResponse([ + 'render' => <<<'EOT' +

+ Hello, + laravel & php & friends +

, +EOT + ]); + + $response->assertSeeTextInOrder(['Hello', 'laravel & php', 'friends']); + } + public function testAssertSeeTextInOrderCanFail(): void { $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that \'foobar baz foo\' contains "foobar" in specified order.'); $response = $this->makeMockResponse([ 'render' => 'foobar baz foo', @@ -746,6 +776,7 @@ public function testAssertDontSeeText(): void public function testAssertDontSeeTextCanFail(): void { $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('Failed asserting that \'foobarbazqux\' does not contain "foobar".'); $response = $this->makeMockResponse([ 'render' => 'foobarbazqux',