@@ -41,4 +41,133 @@ public function test_parse_empty_selector() {
4141 $ this ->assertNull ( $ result );
4242 $ this ->assertSame ( 0 , $ offset );
4343 }
44+
45+ /**
46+ * @ticket 62653
47+ */
48+ public function test_parse_complex_compound_selector () {
49+ $ input = 'div#main.container.large[data-test="value"][role="button"][aria-expanded="false"] ' ;
50+ $ offset = 0 ;
51+ $ sel = WP_CSS_Compound_Selector::parse ( $ input , $ offset );
52+
53+ $ this ->assertNotNull ( $ sel );
54+ $ this ->assertSame ( 'div ' , $ sel ->type_selector ->type );
55+ $ this ->assertSame ( 6 , count ( $ sel ->subclass_selectors ) );
56+
57+ // Check ID selector
58+ $ this ->assertSame ( 'main ' , $ sel ->subclass_selectors [0 ]->id );
59+
60+ // Check class selectors
61+ $ this ->assertSame ( 'container ' , $ sel ->subclass_selectors [1 ]->class_name );
62+ $ this ->assertSame ( 'large ' , $ sel ->subclass_selectors [2 ]->class_name );
63+
64+ // Check attribute selectors
65+ $ this ->assertSame ( 'data-test ' , $ sel ->subclass_selectors [3 ]->name );
66+ $ this ->assertSame ( 'value ' , $ sel ->subclass_selectors [3 ]->value );
67+ $ this ->assertSame ( 'role ' , $ sel ->subclass_selectors [4 ]->name );
68+ $ this ->assertSame ( 'button ' , $ sel ->subclass_selectors [4 ]->value );
69+ $ this ->assertSame ( 'aria-expanded ' , $ sel ->subclass_selectors [5 ]->name );
70+ $ this ->assertSame ( 'false ' , $ sel ->subclass_selectors [5 ]->value );
71+ }
72+
73+ /**
74+ * @ticket 62653
75+ */
76+ public function test_parse_selector_with_only_subclass_selectors () {
77+ $ input = '.class1.class2#id[attr="value"] ' ;
78+ $ offset = 0 ;
79+ $ sel = WP_CSS_Compound_Selector::parse ( $ input , $ offset );
80+
81+ $ this ->assertNotNull ( $ sel );
82+ $ this ->assertNull ( $ sel ->type_selector );
83+ $ this ->assertSame ( 4 , count ( $ sel ->subclass_selectors ) );
84+ }
85+
86+ /**
87+ * @ticket 62653
88+ */
89+ public function test_parse_universal_selector_with_subclass () {
90+ $ input = '*.class#id[attr] ' ;
91+ $ offset = 0 ;
92+ $ sel = WP_CSS_Compound_Selector::parse ( $ input , $ offset );
93+
94+ $ this ->assertNotNull ( $ sel );
95+ $ this ->assertSame ( '* ' , $ sel ->type_selector ->type );
96+ $ this ->assertSame ( 3 , count ( $ sel ->subclass_selectors ) );
97+ }
98+
99+ /**
100+ * @ticket 62653
101+ * @dataProvider data_unsupported_pseudo_selectors
102+ */
103+ public function test_parse_unsupported_pseudo_selectors ( $ input , $ expected_type , $ expected_offset ) {
104+ $ offset = 0 ;
105+ $ sel = WP_CSS_Compound_Selector::parse ( $ input , $ offset );
106+
107+ if ( null === $ expected_type ) {
108+ $ this ->assertNull ( $ sel );
109+ } else {
110+ $ this ->assertNotNull ( $ sel );
111+ $ this ->assertSame ( $ expected_type , $ sel ->type_selector ->type );
112+ }
113+ $ this ->assertSame ( $ expected_offset , $ offset );
114+ }
115+
116+ /**
117+ * Data provider for unsupported pseudo-selectors.
118+ *
119+ * @return array
120+ */
121+ public static function data_unsupported_pseudo_selectors (): array {
122+ return array (
123+ // Pseudo-classes that should be rejected
124+ 'pseudo-class :hover ' => array ( 'a:hover ' , 'a ' , 1 ),
125+ 'pseudo-class :focus ' => array ( 'input:focus ' , 'input ' , 5 ),
126+ 'pseudo-class :active ' => array ( 'button:active ' , 'button ' , 6 ),
127+ 'pseudo-class :visited ' => array ( 'a:visited ' , 'a ' , 1 ),
128+ 'pseudo-class :nth-child ' => array ( 'p:nth-child(2) ' , 'p ' , 1 ),
129+ 'pseudo-class :first-child ' => array ( 'li:first-child ' , 'li ' , 2 ),
130+ 'pseudo-class :last-child ' => array ( 'li:last-child ' , 'li ' , 2 ),
131+ 'pseudo-class :not ' => array ( 'div:not(.class) ' , 'div ' , 3 ),
132+ 'pseudo-class :is ' => array ( 'div:is(.class) ' , 'div ' , 3 ),
133+ 'pseudo-class :where ' => array ( 'div:where(.class) ' , 'div ' , 3 ),
134+ 'pseudo-class :has ' => array ( 'div:has(.class) ' , 'div ' , 3 ),
135+ 'pseudo-class :root ' => array ( 'html:root ' , 'html ' , 4 ),
136+ 'pseudo-class :empty ' => array ( 'div:empty ' , 'div ' , 3 ),
137+ 'pseudo-class :target ' => array ( 'div:target ' , 'div ' , 3 ),
138+ 'pseudo-class :lang ' => array ( 'div:lang(en) ' , 'div ' , 3 ),
139+ 'pseudo-class :dir ' => array ( 'div:dir(ltr) ' , 'div ' , 3 ),
140+ 'pseudo-class :checked ' => array ( 'input:checked ' , 'input ' , 5 ),
141+ 'pseudo-class :disabled ' => array ( 'input:disabled ' , 'input ' , 5 ),
142+ 'pseudo-class :enabled ' => array ( 'input:enabled ' , 'input ' , 5 ),
143+ 'pseudo-class :required ' => array ( 'input:required ' , 'input ' , 5 ),
144+ 'pseudo-class :optional ' => array ( 'input:optional ' , 'input ' , 5 ),
145+ 'pseudo-class :valid ' => array ( 'input:valid ' , 'input ' , 5 ),
146+ 'pseudo-class :invalid ' => array ( 'input:invalid ' , 'input ' , 5 ),
147+
148+ // Pseudo-elements that should be rejected
149+ 'pseudo-element ::before ' => array ( 'div::before ' , 'div ' , 3 ),
150+ 'pseudo-element ::after ' => array ( 'div::after ' , 'div ' , 3 ),
151+ 'pseudo-element ::first-line ' => array ( 'p::first-line ' , 'p ' , 1 ),
152+ 'pseudo-element ::first-letter ' => array ( 'p::first-letter ' , 'p ' , 1 ),
153+ 'pseudo-element ::selection ' => array ( 'p::selection ' , 'p ' , 1 ),
154+ 'pseudo-element ::backdrop ' => array ( 'dialog::backdrop ' , 'dialog ' , 6 ),
155+ 'pseudo-element ::placeholder ' => array ( 'input::placeholder ' , 'input ' , 5 ),
156+ 'pseudo-element ::marker ' => array ( 'li::marker ' , 'li ' , 2 ),
157+ 'pseudo-element ::cue ' => array ( 'video::cue ' , 'video ' , 5 ),
158+ 'pseudo-element ::slotted ' => array ( 'slot::slotted(.class) ' , 'slot ' , 4 ),
159+
160+ // Legacy single-colon pseudo-elements
161+ 'legacy :before ' => array ( 'div:before ' , 'div ' , 3 ),
162+ 'legacy :after ' => array ( 'div:after ' , 'div ' , 3 ),
163+ 'legacy :first-line ' => array ( 'p:first-line ' , 'p ' , 1 ),
164+ 'legacy :first-letter ' => array ( 'p:first-letter ' , 'p ' , 1 ),
165+
166+ // Invalid pseudo-selectors
167+ 'invalid :: ' => array ( 'div:: ' , 'div ' , 3 ),
168+ 'invalid : alone ' => array ( 'div: ' , 'div ' , 3 ),
169+ 'invalid :123 ' => array ( 'div:123 ' , 'div ' , 3 ),
170+ 'invalid :@#$ ' => array ( 'div:@#$ ' , 'div ' , 3 ),
171+ );
172+ }
44173}
0 commit comments