diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b1d3624..e72b2bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ([#773](https://github.com/MyIntervals/emogrifier/pull/773)) ### Fixed +- Add missing `` element when there's a `
` element + ([#844](https://github.com/MyIntervals/emogrifier/pull/844), + [#853](https://github.com/MyIntervals/emogrifier/pull/853)) - Fix mapping width/height when decimal is used ([#845](https://github.com/MyIntervals/emogrifier/pull/845)) - Actually use the specified PHP version on GitHub actions diff --git a/src/HtmlProcessor/AbstractHtmlProcessor.php b/src/HtmlProcessor/AbstractHtmlProcessor.php index cf18dadd..c6c2638e 100644 --- a/src/HtmlProcessor/AbstractHtmlProcessor.php +++ b/src/HtmlProcessor/AbstractHtmlProcessor.php @@ -268,11 +268,15 @@ private function addContentTypeMetaTag(string $html): string // We are trying to insert the meta tag to the right spot in the DOM. // If we just prepended it to the HTML, we would lose attributes set to the HTML tag. - $hasHeadTag = \stripos($html, ']/i', $html); $hasHtmlTag = \stripos($html, '/i', '' . static::CONTENT_TYPE_META_TAG, $html); + $reworkedHtml = \preg_replace( + '/])([^>]*+)>/i', + '' . static::CONTENT_TYPE_META_TAG, + $html + ); } elseif ($hasHtmlTag) { $reworkedHtml = \preg_replace( '//i', diff --git a/tests/Unit/CssInlinerTest.php b/tests/Unit/CssInlinerTest.php index 031f5ae6..3a55f4a3 100644 --- a/tests/Unit/CssInlinerTest.php +++ b/tests/Unit/CssInlinerTest.php @@ -2252,6 +2252,18 @@ public function inlineCssAppliesCssToMatchingElementsAndKeepsRuleWithPseudoCompo self::assertContainsCss('', $result); } + /** + * @test + */ + public function inlineCssKeepsRuleWithPseudoComponentInMatchingSelectorForHtmlWithHeader() + { + $subject = $this->buildDebugSubject('
foo
'); + + $subject->inlineCss('a:hover { color: green; }'); + + self::assertContainsCss('', $subject->render()); + } + /** * @return string[][] */ diff --git a/tests/Unit/HtmlProcessor/AbstractHtmlProcessorTest.php b/tests/Unit/HtmlProcessor/AbstractHtmlProcessorTest.php index 5c3dcfce..f22acc84 100644 --- a/tests/Unit/HtmlProcessor/AbstractHtmlProcessorTest.php +++ b/tests/Unit/HtmlProcessor/AbstractHtmlProcessorTest.php @@ -175,6 +175,8 @@ public function contentWithoutHeadTagDataProvider(): array 'doctype only' => [''], 'body content only' => ['

Hello

'], 'BODY element' => [''], + 'HEADER element' => ['
'], + 'META element (implicit HEAD)' => [''], ]; } @@ -185,13 +187,57 @@ public function contentWithoutHeadTagDataProvider(): array * * @dataProvider contentWithoutHeadTagDataProvider */ - public function addsMissingHeadTag(string $html) + public function addsMissingHeadTagOnlyOnce(string $html) { $subject = TestingHtmlProcessor::fromHtml($html); $result = $subject->render(); - self::assertContains('', $result); + $headTagCount = \substr_count($result, ''); + self::assertSame(1, $headTagCount); + } + + /** + * @return string[][] + */ + public function contentWithHeadTagDataProvider(): array + { + return [ + 'HEAD element' => [' '], + 'HEAD element, capitalized' => [''], + '(invalid) void HEAD element' => [''], + 'HEAD element with attribute' => [' '], + 'HEAD element and HEADER element' => ['
'], + ]; + } + + /** + * @test + * + * @param string $html + * + * @dataProvider contentWithHeadTagDataProvider + */ + public function notAddsSecondHeadTag(string $html) + { + $subject = TestingHtmlProcessor::fromHtml($html); + + $result = $subject->render(); + + $headTagCount = \preg_match_all('%]%', $result); + self::assertSame(1, $headTagCount); + } + + /** + * @test + */ + public function preservesHeadAttributes() + { + $subject = TestingHtmlProcessor::fromHtml(' '); + + $result = $subject->render(); + + self::assertContains('', $result); } /** @@ -319,22 +365,51 @@ public function keepsExistingDocumentType(string $documentType) /** * @test + * + * @param string $html + * + * @dataProvider contentWithoutHeadTagDataProvider + * @dataProvider contentWithHeadTagDataProvider */ - public function addsMissingContentTypeMetaTag() + public function addsMissingContentTypeMetaTagOnlyOnce(string $html) { - $subject = TestingHtmlProcessor::fromHtml('

Hello

'); + $subject = TestingHtmlProcessor::fromHtml($html); $result = $subject->render(); - self::assertContains('', $result); + $numberOfContentTypeMetaTags = \substr_count( + $result, + '' + ); + self::assertSame(1, $numberOfContentTypeMetaTags); + } + + /** + * @return string[][] + */ + public function htmlAroundContentTypeDataProvider(): array + { + return [ + 'HTML and HEAD element' => ['', ' '], + 'HTML and HEAD element, HTML end tag omitted' => ['', ' '], + 'HEAD element only' => ['', ' '], + 'HEAD element with attribute' => ['', ' '], + 'HTML, HEAD, and BODY with HEADER elements' + => ['', '
'], + ]; } /** * @test + * + * @param string $htmlBefore + * @param string $htmlAfter + * + * @dataProvider htmlAroundContentTypeDataProvider */ - public function notAddsSecondContentTypeMetaTag() + public function notAddsSecondContentTypeMetaTag(string $htmlBefore, string $htmlAfter) { - $html = ' '; + $html = $htmlBefore . '' . $htmlAfter; $subject = TestingHtmlProcessor::fromHtml($html); $result = $subject->render();