@@ -78,6 +78,28 @@ public static function provideSelectorsAndSpecificities(): array
7878 ];
7979 }
8080
81+ /**
82+ * @return array<non-empty-string, array{0: non-empty-string}>
83+ */
84+ public static function provideSelectorsWithEscapedQuotes (): array
85+ {
86+ return [
87+ 'escaped double quote in double-quoted attribute ' => ['a[href="test \\"value"] ' ],
88+ 'escaped single quote in single-quoted attribute ' => ['a[href= \'test \\\'value \'] ' ],
89+ 'multiple escaped double quotes in double-quoted attribute ' => ['a[title="say \\"hello \\" world"] ' ],
90+ 'multiple escaped single quotes in single-quoted attribute ' => ['a[title= \'say \\\'hello \\\' world \'] ' ],
91+ 'escaped quote at start of attribute value ' => ['a[data-test=" \\"start"] ' ],
92+ 'escaped quote at end of attribute value ' => ['a[data-test="end \\""] ' ],
93+ 'escaped backslash followed by quote ' => ['a[data-test="test \\\\"] ' ],
94+ 'escaped backslash before escaped quote ' => ['a[data-test="test \\\\\\"value"] ' ],
95+ 'triple backslash before quote ' => ['a[data-test="test \\\\\\""] ' ],
96+ 'escaped single quotes in selector itself, with other escaped characters '
97+ => ['.before \\:content- \\[ \\\'\\\'\\]:before ' ],
98+ 'escaped double quotes in selector itself, with other escaped characters '
99+ => ['.before \\:content- \\[ \\" \\" \\]:before ' ],
100+ ];
101+ }
102+
81103 /**
82104 * @test
83105 *
@@ -94,6 +116,45 @@ public function parsesValidSelector(string $selector): void
94116 self ::assertSame ($ selector , $ result ->getSelector ());
95117 }
96118
119+ /**
120+ * @test
121+ */
122+ public function parsingAttributeWithEscapedQuoteDoesNotPrematurelyCloseString (): void
123+ {
124+ $ selector = 'input[placeholder="Enter \\"quoted \\" text here"] ' ;
125+
126+ $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
127+
128+ self ::assertInstanceOf (Selector::class, $ result );
129+ self ::assertSame ($ selector , $ result ->getSelector ());
130+ }
131+
132+ /**
133+ * @test
134+ */
135+ public function parseDistinguishesEscapedFromUnescapedQuotes (): void
136+ {
137+ // One backslash = escaped quote (should not close string)
138+ $ selector = 'a[data-value="test \\"more"] ' ;
139+
140+ $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
141+
142+ self ::assertSame ($ selector , $ result ->getSelector ());
143+ }
144+
145+ /**
146+ * @test
147+ */
148+ public function parseHandlesEvenNumberOfBackslashesBeforeQuote (): void
149+ {
150+ // Two backslashes = escaped backslash + unescaped quote (should close string)
151+ $ selector = 'a[data-value="test \\\\"] ' ;
152+
153+ $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
154+
155+ self ::assertSame ($ selector , $ result ->getSelector ());
156+ }
157+
97158 /**
98159 * @return array<non-empty-string, array{0: string}>
99160 */
@@ -371,65 +432,4 @@ public function getArrayRepresentationThrowsException(): void
371432
372433 $ subject ->getArrayRepresentation ();
373434 }
374-
375- /**
376- * @return array<non-empty-string, array{0: non-empty-string}>
377- */
378- public static function provideSelectorsWithEscapedQuotes (): array
379- {
380- return [
381- 'escaped double quote in double-quoted attribute ' => ['a[href="test \\"value"] ' ],
382- 'escaped single quote in single-quoted attribute ' => ['a[href= \'test \\\'value \'] ' ],
383- 'multiple escaped double quotes in double-quoted attribute ' => ['a[title="say \\"hello \\" world"] ' ],
384- 'multiple escaped single quotes in single-quoted attribute ' => ['a[title= \'say \\\'hello \\\' world \'] ' ],
385- 'escaped quote at start of attribute value ' => ['a[data-test=" \\"start"] ' ],
386- 'escaped quote at end of attribute value ' => ['a[data-test="end \\""] ' ],
387- 'escaped backslash followed by quote ' => ['a[data-test="test \\\\"] ' ],
388- 'escaped backslash before escaped quote ' => ['a[data-test="test \\\\\\"value"] ' ],
389- 'triple backslash before quote ' => ['a[data-test="test \\\\\\""] ' ],
390- 'escaped single quotes in selector itself, with other escaped characters '
391- => ['.before \\:content- \\[ \\\'\\\'\\]:before ' ],
392- 'escaped double quotes in selector itself, with other escaped characters '
393- => ['.before \\:content- \\[ \\" \\" \\]:before ' ],
394- ];
395- }
396-
397- /**
398- * @test
399- */
400- public function parsingAttributeWithEscapedQuoteDoesNotPrematurelyCloseString (): void
401- {
402- $ selector = 'input[placeholder="Enter \\"quoted \\" text here"] ' ;
403-
404- $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
405-
406- self ::assertInstanceOf (Selector::class, $ result );
407- self ::assertSame ($ selector , $ result ->getSelector ());
408- }
409-
410- /**
411- * @test
412- */
413- public function parseDistinguishesEscapedFromUnescapedQuotes (): void
414- {
415- // One backslash = escaped quote (should not close string)
416- $ selector = 'a[data-value="test \\"more"] ' ;
417-
418- $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
419-
420- self ::assertSame ($ selector , $ result ->getSelector ());
421- }
422-
423- /**
424- * @test
425- */
426- public function parseHandlesEvenNumberOfBackslashesBeforeQuote (): void
427- {
428- // Two backslashes = escaped backslash + unescaped quote (should close string)
429- $ selector = 'a[data-value="test \\\\"] ' ;
430-
431- $ result = Selector::parse (new ParserState ($ selector , Settings::create ()));
432-
433- self ::assertSame ($ selector , $ result ->getSelector ());
434- }
435435}
0 commit comments