55namespace Sabberworm \CSS \Tests \Unit \Property ;
66
77use PHPUnit \Framework \TestCase ;
8+ use Sabberworm \CSS \Comment \Comment ;
9+ use Sabberworm \CSS \Parsing \ParserState ;
10+ use Sabberworm \CSS \Parsing \UnexpectedTokenException ;
811use Sabberworm \CSS \Property \Selector ;
912use Sabberworm \CSS \Renderable ;
13+ use Sabberworm \CSS \Settings ;
14+ use TRegx \PhpUnit \DataProviders \DataProvider ;
1015
1116/**
1217 * @covers \Sabberworm\CSS\Property\Selector
@@ -73,6 +78,21 @@ public static function provideSelectorsAndSpecificities(): array
7378 ];
7479 }
7580
81+ /**
82+ * @test
83+ *
84+ * @param non-empty-string $selector
85+ *
86+ * @dataProvider provideSelectorsAndSpecificities
87+ */
88+ public function parsesValidSelector (string $ selector ): void
89+ {
90+ $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
91+
92+ self ::assertInstanceOf (Selector::class, $ result );
93+ self ::assertSame ($ result ->getSelector (), $ selector );
94+ }
95+
7696 /**
7797 * @return array<non-empty-string, array{0: string}>
7898 */
@@ -93,6 +113,146 @@ public static function provideInvalidSelectors(): array
93113 ];
94114 }
95115
116+ /**
117+ * @return array<non-empty-string, array{0: string}>
118+ */
119+ public static function provideInvalidSelectorsForParse (): array
120+ {
121+ return [
122+ 'empty string ' => ['' ],
123+ 'whitespace only ' => [" \t\n\r" ],
124+ 'not missing closing brace ' => [':not(a ' ],
125+ 'not missing opening brace ' => [':not a) ' ],
126+ 'attribute value missing closing single quote ' => ['a[href= \'#top] ' ],
127+ 'attribute value missing closing double quote ' => ['a[href="#top] ' ],
128+ ];
129+ }
130+
131+ /**
132+ * @test
133+ *
134+ * @param non-empty-string $selector
135+ *
136+ * @dataProvider provideInvalidSelectors
137+ * @dataProvider provideInvalidSelectorsForParse
138+ */
139+ public function parseThrowsExceptionWithInvalidSelector (string $ selector ): void
140+ {
141+ $ this ->expectException (UnexpectedTokenException::class);
142+
143+ Selector::parse (new ParserState ($ selector , Settings::create ()));
144+ }
145+
146+ /**
147+ * @return array<non-empty-string, array{0: non-empty-string}>
148+ */
149+ public static function provideStopCharacters (): array
150+ {
151+ return [
152+ ', ' => ['{ ' ],
153+ '{ ' => ['{ ' ],
154+ '} ' => ['} ' ],
155+ ];
156+ }
157+
158+ /**
159+ * @return DataProvider<non-empty-string, array{0: non-empty-string, 1: non-empty-string}>
160+ */
161+ public function provideStopCharactersAndValidSelectors (): DataProvider
162+ {
163+ return DataProvider::cross (self ::provideStopCharacters (), self ::provideSelectorsAndSpecificities ());
164+ }
165+
166+ /**
167+ * @test
168+ *
169+ * @param non-empty-string $stopCharacter
170+ * @param non-empty-string $selector
171+ *
172+ * @dataProvider provideStopCharactersAndValidSelectors
173+ */
174+ public function parseDoesNotConsumeStopCharacter (string $ stopCharacter , string $ selector ): void
175+ {
176+ $ subject = new ParserState ($ selector . $ stopCharacter , Settings::create ());
177+
178+ Selector::parse ($ subject );
179+
180+ self ::assertSame ($ stopCharacter , $ subject ->peek ());
181+ }
182+
183+ /**
184+ * @return array<non-empty-string, array{0: non-empty-string, 1: non-empty-string}>
185+ */
186+ public static function provideSelectorsWithAndWithoutComment (): array
187+ {
188+ return [
189+ 'comment before ' => ['/*comment*/body ' , 'body ' ],
190+ 'comment after ' => ['body/*comment*/ ' , 'body ' ],
191+ 'comment within ' => ['./*comment*/teapot ' , '.teapot ' ],
192+ 'comment within function ' => [':not(#your-mug,/*comment*/.their-mug) ' , ':not(#your-mug,.their-mug) ' ],
193+ ];
194+ }
195+
196+ /**
197+ * @test
198+ *
199+ * @param non-empty-string $selectorWith
200+ * @param non-empty-string $selectorWithout
201+ *
202+ * @dataProvider provideSelectorsWithAndWithoutComment
203+ */
204+ public function parsesSelectorWithComment (string $ selectorWith , string $ selectorWithout ): void
205+ {
206+ $ result = Selector::parse (new ParserState ($ selectorWith , Settings::create ()));
207+
208+ self ::assertInstanceOf (Selector::class, $ result );
209+ self ::assertSame ($ selectorWithout , $ result ->getSelector ());
210+ }
211+
212+ /**
213+ * @test
214+ *
215+ * @param non-empty-string $selector
216+ *
217+ * @dataProvider provideSelectorsWithAndWithoutComment
218+ */
219+ public function parseExtractsCommentFromSelector (string $ selector ): void
220+ {
221+ $ result = [];
222+ Selector::parse (new ParserState ($ selector , Settings::create ()), $ result );
223+
224+ self ::assertArrayHasKey (0 , $ result );
225+ self ::assertInstanceOf (Comment::class, $ result [0 ]);
226+ self ::assertSame ('comment ' , $ result [0 ]->getComment ());
227+ }
228+
229+ /**
230+ * @test
231+ */
232+ public function parsesSelectorWithTwoComments (): void
233+ {
234+ $ result = Selector::parse (new ParserState ('/*comment1*/a/*comment2*/ ' , Settings::create ()));
235+
236+ self ::assertInstanceOf (Selector::class, $ result );
237+ self ::assertSame ('a ' , $ result ->getSelector ());
238+ }
239+
240+ /**
241+ * @test
242+ */
243+ public function parseExtractsTwoCommentsFromSelector (): void
244+ {
245+ $ result = [];
246+ Selector::parse (new ParserState ('/*comment1*/a/*comment2*/ ' , Settings::create ()), $ result );
247+
248+ self ::assertArrayHasKey (0 , $ result );
249+ self ::assertInstanceOf (Comment::class, $ result [0 ]);
250+ self ::assertSame ('comment1 ' , $ result [0 ]->getComment ());
251+ self ::assertArrayHasKey (1 , $ result );
252+ self ::assertInstanceOf (Comment::class, $ result [1 ]);
253+ self ::assertSame ('comment2 ' , $ result [1 ]->getComment ());
254+ }
255+
96256 /**
97257 * @test
98258 *
0 commit comments