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('');
+
+ $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('%'],
+ ];
}
/**
* @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();
]%', $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'
+ => ['
', '