Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
[BUGFIX] Allow commas in attributes in setSelectors
This fixes an issue noted in #1324.
  • Loading branch information
JakeQZ committed Dec 9, 2025
commit e7a4387854c3ca120e1b6fb2e4d0f0e656a15ebc
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Please also have a look at our

### Fixed

- Support attribute selectors with values containing commas in
`DeclarationBlock::setSelectors()` (#1419)
- Allow `removeDeclarationBlockBySelector()` to be order-insensitve (#1406)
- Fix parsing of `calc` expressions when a newline immediately precedes or
follows a `+` or `-` operator (#1399)
Expand Down
12 changes: 10 additions & 2 deletions src/RuleSet/DeclarationBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Sabberworm\CSS\Property\KeyframeSelector;
use Sabberworm\CSS\Property\Selector;
use Sabberworm\CSS\Rule\Rule;
use Sabberworm\CSS\Settings;

/**
* This class represents a `RuleSet` constrained by a `Selector`.
Expand Down Expand Up @@ -98,7 +99,14 @@ public function setSelectors($selectors, ?CSSList $list = null): void
if (\is_array($selectors)) {
$selectorsToSet = $selectors;
} else {
$selectorsToSet = \explode(',', $selectors);
// A string of comma-separated selectors requires parsing.
// Parse as if it's the opening part of a rule.
$parserState = new ParserState($selectors . '{', Settings::create());
$selectorsToSet = self::parseSelectors($parserState);
$parserState->consume('{'); // throw exception if this is not next
if (!$parserState->isEnd()) {
throw new UnexpectedTokenException('EOF', $parserState->peek(5));
}
}

// Convert all items to a `Selector` if not already
Expand Down Expand Up @@ -261,7 +269,7 @@ public function render(OutputFormat $outputFormat): string
*
* @throws UnexpectedTokenException
*/
private static function parseSelectors(ParserState $parserState, array &$comments): array
private static function parseSelectors(ParserState $parserState, array &$comments = []): array
{
$selectors = [];
$selectorParts = [];
Expand Down
55 changes: 55 additions & 0 deletions tests/Unit/RuleSet/DeclarationBlockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Sabberworm\CSS\CSSElement;
use Sabberworm\CSS\CSSList\CSSListItem;
use Sabberworm\CSS\Parsing\ParserState;
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
use Sabberworm\CSS\Position\Positionable;
use Sabberworm\CSS\Property\Selector;
use Sabberworm\CSS\Rule\Rule;
Expand Down Expand Up @@ -293,4 +294,58 @@ public function setSelectorsIgnoresKeys(): void

self::assertSame([0, 1], \array_keys($result));
}

/**
* @test
*
* @param non-empty-string $selector
*
* @dataProvider provideSelector
*/
public function setSelectorsSetsSingleSelectorProvidedAsString(string $selector): void
{
$subject = new DeclarationBlock();

$subject->setSelectors($selector);

$result = $subject->getSelectors();
self::assertSame([$selector], self::getSelectorsAsStrings($subject));
}

/**
* @test
*
* @param non-empty-string $firstSelector
* @param non-empty-string $secondSelector
*
* @dataProvider provideTwoSelectors
*/
public function setSelectorsSetsTwoCommaSeparatedSelectorsProvidedAsString(
string $firstSelector,
string $secondSelector
): void {
$joinedSelectors = $firstSelector . ', ' . $secondSelector;
$subject = new DeclarationBlock();

$subject->setSelectors($joinedSelectors);

$result = $subject->getSelectors();
self::assertSame([$firstSelector, $secondSelector], self::getSelectorsAsStrings($subject));
}

/**
* @test
*
* @param non-empty-string $selector
*
* @dataProvider provideInvalidSelector
*/
public function setSelectorsThrowsExceptionWithInvalidSelector(string $selector): void
{
$this->expectException(UnexpectedTokenException::class);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check for the exception message.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the messages and found they were unhelpful, so have fixed that.

I also found that the exceptions thrown by the code when { or end of string were not as expected were not being tested, so have added more test data for that.


$subject = new DeclarationBlock();

$subject->setSelectors($selector);
}
}