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,148 @@ 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+ 'space ' => [' ' ],
124+ 'tab ' => ["\t" ],
125+ 'line feed ' => ["\n" ],
126+ 'carriage return ' => ["\r" ],
127+ 'a `:not` missing the closing brace ' => [':not(a ' ],
128+ 'a `:not` missing the opening brace ' => [':not a) ' ],
129+ 'attribute value missing closing single quote ' => ['a[href= \'#top] ' ],
130+ 'attribute value missing closing double quote ' => ['a[href="#top] ' ],
131+ 'attribute value with mismatched quotes, single quote opening ' => ['a[href= \'#top"] ' ],
132+ 'attribute value with mismatched quotes, double quote opening ' => ['a[href="#top \'] ' ],
133+ ];
134+ }
135+
136+ /**
137+ * @test
138+ *
139+ * @param non-empty-string $selector
140+ *
141+ * @dataProvider provideInvalidSelectors
142+ * @dataProvider provideInvalidSelectorsForParse
143+ */
144+ public function parseThrowsExceptionWithInvalidSelector (string $ selector ): void
145+ {
146+ $ this ->expectException (UnexpectedTokenException::class);
147+
148+ Selector::parse (new ParserState ($ selector , Settings::create ()));
149+ }
150+
151+ /**
152+ * @return array<non-empty-string, array{0: non-empty-string}>
153+ */
154+ public static function provideStopCharacters (): array
155+ {
156+ return [
157+ ', ' => [', ' ],
158+ '{ ' => ['{ ' ],
159+ '} ' => ['} ' ],
160+ ];
161+ }
162+
163+ /**
164+ * @return DataProvider<non-empty-string, array{0: non-empty-string, 1: non-empty-string}>
165+ */
166+ public function provideStopCharactersAndValidSelectors (): DataProvider
167+ {
168+ return DataProvider::cross (self ::provideStopCharacters (), self ::provideSelectorsAndSpecificities ());
169+ }
170+
171+ /**
172+ * @test
173+ *
174+ * @param non-empty-string $stopCharacter
175+ * @param non-empty-string $selector
176+ *
177+ * @dataProvider provideStopCharactersAndValidSelectors
178+ */
179+ public function parseDoesNotConsumeStopCharacter (string $ stopCharacter , string $ selector ): void
180+ {
181+ $ subject = new ParserState ($ selector . $ stopCharacter , Settings::create ());
182+
183+ Selector::parse ($ subject );
184+
185+ self ::assertSame ($ stopCharacter , $ subject ->peek ());
186+ }
187+
188+ /**
189+ * @return array<non-empty-string, array{0: non-empty-string, 1: non-empty-string}>
190+ */
191+ public static function provideSelectorsWithAndWithoutComment (): array
192+ {
193+ return [
194+ 'comment before ' => ['/*comment*/body ' , 'body ' ],
195+ 'comment after ' => ['body/*comment*/ ' , 'body ' ],
196+ 'comment within ' => ['./*comment*/teapot ' , '.teapot ' ],
197+ 'comment within function ' => [':not(#your-mug,/*comment*/.their-mug) ' , ':not(#your-mug,.their-mug) ' ],
198+ ];
199+ }
200+
201+ /**
202+ * @test
203+ *
204+ * @param non-empty-string $selectorWith
205+ * @param non-empty-string $selectorWithout
206+ *
207+ * @dataProvider provideSelectorsWithAndWithoutComment
208+ */
209+ public function parsesSelectorWithComment (string $ selectorWith , string $ selectorWithout ): void
210+ {
211+ $ result = Selector::parse (new ParserState ($ selectorWith , Settings::create ()));
212+
213+ self ::assertInstanceOf (Selector::class, $ result );
214+ self ::assertSame ($ selectorWithout , $ result ->getSelector ());
215+ }
216+
217+ /**
218+ * @test
219+ *
220+ * @param non-empty-string $selector
221+ *
222+ * @dataProvider provideSelectorsWithAndWithoutComment
223+ */
224+ public function parseExtractsCommentFromSelector (string $ selector ): void
225+ {
226+ $ result = [];
227+ Selector::parse (new ParserState ($ selector , Settings::create ()), $ result );
228+
229+ self ::assertInstanceOf (Comment::class, $ result [0 ]);
230+ self ::assertSame ('comment ' , $ result [0 ]->getComment ());
231+ }
232+
233+ /**
234+ * @test
235+ */
236+ public function parsesSelectorWithTwoComments (): void
237+ {
238+ $ result = Selector::parse (new ParserState ('/*comment1*/a/*comment2*/ ' , Settings::create ()));
239+
240+ self ::assertInstanceOf (Selector::class, $ result );
241+ self ::assertSame ('a ' , $ result ->getSelector ());
242+ }
243+
244+ /**
245+ * @test
246+ */
247+ public function parseExtractsTwoCommentsFromSelector (): void
248+ {
249+ $ result = [];
250+ Selector::parse (new ParserState ('/*comment1*/a/*comment2*/ ' , Settings::create ()), $ result );
251+
252+ self ::assertInstanceOf (Comment::class, $ result [0 ]);
253+ self ::assertSame ('comment1 ' , $ result [0 ]->getComment ());
254+ self ::assertInstanceOf (Comment::class, $ result [1 ]);
255+ self ::assertSame ('comment2 ' , $ result [1 ]->getComment ());
256+ }
257+
96258 /**
97259 * @test
98260 *
0 commit comments