Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add CssInliner::getMatchingUninlinableSelectors #707

Merged
merged 3 commits into from
Sep 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## x.y.z

### Added
- Add `CssInliner::getMatchingUninlinableSelectors`
([#380](https://github.com/MyIntervals/emogrifier/issues/380),
[#707](https://github.com/MyIntervals/emogrifier/pull/707))
- Add tests for `:nth-child` and `:nth-of-type`
([#71](https://github.com/MyIntervals/emogrifier/issues/71),
[#698](https://github.com/MyIntervals/emogrifier/pull/698))
Expand Down
21 changes: 20 additions & 1 deletion src/Emogrifier/CssInliner.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class CssInliner extends AbstractHtmlProcessor
*
* @var string[][]
*/
private $matchingUninlinableCssRules = [];
private $matchingUninlinableCssRules = null;

/**
* Emogrifier will throw Exceptions when it encounters an error instead of silently ignoring them.
Expand Down Expand Up @@ -626,6 +626,25 @@ private function attributeValueIsImportant($attributeValue)
return \strtolower(\substr(\trim($attributeValue), -10)) === '!important';
}

/**
* Gets the array of selectors present in the CSS provided to `inlineCss()` for which the declarations could not be
* applied as inline styles, but which may affect elements in the HTML. The relevant CSS will have been placed in a
* `<style>` element. The selectors may include those used within `@media` rules or those involving dynamic
* pseudo-classes (such as `:hover`) or pseudo-elements (such as `::after`).
*
* @return string[]
*
* @throws \BadMethodCallException if `inlineCss` has not been called first
*/
public function getMatchingUninlinableSelectors()
{
if ($this->matchingUninlinableCssRules === null) {
throw new \BadMethodCallException('inlineCss must be called first', 1568385221);
}

return \array_column($this->matchingUninlinableCssRules, 'selector');
}

/**
* Determines which of `$cssRules` actually apply to `$this->domDocument`, and sets them in
* `$this->matchingUninlinableCssRules`.
Expand Down
77 changes: 77 additions & 0 deletions tests/Unit/CssInlinerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2618,6 +2618,83 @@ public function inlineCssNotInDebugModeKeepsInvalidOrUnrecognizedSelectorsInMedi
self::assertContainsCss($css, $subject->render());
}

/**
* @test
*/
public function getMatchingUninlinableSelectorsThrowsExceptionIfInlineCssNotCalled()
{
$this->expectException(\BadMethodCallException::class);

$subject = $this->buildDebugSubject('<html></html>');

$subject->getMatchingUninlinableSelectors();
}

/**
* @return string[][][]
*/
public function matchingUninlinableSelectorsDataProvider()
{
return [
'1 matching uninlinable selector' => [['p:hover']],
'2 matching uninlinable selectors' => [['p:hover', 'p::after']],
];
}

/**
* @test
*
* @param string[] $selectors
*
* @dataProvider matchingUninlinableSelectorsDataProvider
*/
public function getMatchingUninlinableSelectorsReturnsMatchingUninlinableSelectors(array $selectors)
{
$css = \implode(' ', \array_map(
static function ($selector) {
return $selector . ' { color: green; }';
},
$selectors
));
$subject = $this->buildDebugSubject('<html><p>foo</p></html>');
$subject->inlineCss($css);

$result = $subject->getMatchingUninlinableSelectors();

foreach ($selectors as $selector) {
static::assertContains($selector, $result);
}
}

/**
* @return string[][]
*/
public function nonMatchingOrInlinableSelectorDataProvider()
{
return [
'non matching uninlinable selector' => ['a:hover'],
'matching inlinable selector' => ['p'],
'non matching inlinable selector' => ['a'],
];
}

/**
* @test
*
* @param string $selector
*
* @dataProvider nonMatchingOrInlinableSelectorDataProvider
*/
public function getMatchingUninlinableSelectorsNotReturnsNonMatchingOrInlinableSelector($selector)
{
$subject = $this->buildDebugSubject('<html><p>foo</p></html>');
$subject->inlineCss($selector . ' { color: red; }');

$result = $subject->getMatchingUninlinableSelectors();

static::assertSame([], $result);
}

/**
* @test
*/
Expand Down