Skip to content

Commit 733cb50

Browse files
committed
Add additional tests
1 parent 5f06f84 commit 733cb50

9 files changed

+375
-0
lines changed

tests/phpunit/tests/html-api/wpCssAttributeSelector.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,32 @@ public static function data_attribute_selectors(): array {
8585
"Invalid: [att='val\\n']" => array( "[att='val\n']" ),
8686
'Invalid: [att=val i ' => array( '[att=val i ' ),
8787
'Invalid: [att="val"ix' => array( '[att="val"ix' ),
88+
89+
// Additional malformed selector tests
90+
'Invalid: [att=val' => array( '[att=val' ),
91+
'Invalid: [att="val' => array( '[att="val' ),
92+
'Invalid: [att=val"' => array( '[att=val"' ),
93+
'Invalid: [att =val i i]' => array( '[att =val i i]' ),
94+
'Invalid: [att~=]' => array( '[att~=]' ),
95+
'Invalid: [att^=]' => array( '[att^=]' ),
96+
'Invalid: [att$=]' => array( '[att$=]' ),
97+
'Invalid: [att*=]' => array( '[att*=]' ),
98+
'Invalid: [att|=]' => array( '[att|=]' ),
99+
'Invalid: [att==val]' => array( '[att==val]' ),
100+
'Invalid: [att =~ val]' => array( '[att =~ val]' ),
101+
'Invalid: [att!val]' => array( '[att!val]' ),
102+
'Invalid: [att?=val]' => array( '[att?=val]' ),
103+
'Invalid: [att =val x]' => array( '[att =val x]' ),
104+
'Invalid: [att =val ii]' => array( '[att =val ii]' ),
105+
'Invalid: [att =val ss]' => array( '[att =val ss]' ),
106+
107+
// Namespace attribute selectors (currently unsupported)
108+
'Invalid: [ns|attr]' => array( '[ns|attr]' ),
109+
'Invalid: [*|attr]' => array( '[*|attr]' ),
110+
'Invalid: [|attr]' => array( '[|attr]' ),
111+
'Invalid: [xml|attr]' => array( '[xml|lang]' ),
112+
'Invalid: [svg|attr]' => array( '[svg|viewBox]' ),
113+
'Invalid: [xmlns|attr]' => array( '[xmlns:xlink]' ),
88114
);
89115
}
90116
}

tests/phpunit/tests/html-api/wpCssClassSelector.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,20 @@ public static function data_class_selectors(): array {
4141
'escaped .\31 23' => array( '.\\31 23', '123', '' ),
4242
'with descendant .\31 23 div' => array( '.\\31 23 div', '123', ' div' ),
4343

44+
// Additional edge cases
45+
'multiple dots .a.b' => array( '.a.b', 'a', '.b' ),
46+
'escaped dot .\\2e class' => array( '.\\2e class', '.class', '' ),
47+
'unicode class .café' => array( '.café', 'café', '' ),
48+
'hyphen class .my-class' => array( '.my-class', 'my-class', '' ),
49+
'underscore class .my_class' => array( '.my_class', 'my_class', '' ),
50+
'long class name' => array( '.very-long-class-name-with-many-hyphens', 'very-long-class-name-with-many-hyphens', '' ),
51+
4452
'not class foo' => array( 'foo' ),
4553
'not class #bar' => array( '#bar' ),
4654
'not valid .1foo' => array( '.1foo' ),
55+
'empty after dot' => array( '.' ),
56+
'space after dot' => array( '. ' ),
57+
'invalid after dot' => array( '.@invalid' ),
4758
);
4859
}
4960
}

tests/phpunit/tests/html-api/wpCssComplexSelector.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,74 @@ public function test_parse_empty_complex_selector() {
6868
$result = WP_CSS_Complex_Selector::parse( $input, $offset );
6969
$this->assertNull( $result );
7070
}
71+
72+
/**
73+
* @ticket 62653
74+
*/
75+
public function test_parse_unsupported_next_sibling_combinator() {
76+
$input = 'h1 + p';
77+
$offset = 0;
78+
$result = WP_CSS_Complex_Selector::parse( $input, $offset );
79+
$this->assertNull( $result, 'Next sibling combinator (+) should not be supported' );
80+
}
81+
82+
/**
83+
* @ticket 62653
84+
*/
85+
public function test_parse_unsupported_subsequent_sibling_combinator() {
86+
$input = 'h1 ~ p';
87+
$offset = 0;
88+
$result = WP_CSS_Complex_Selector::parse( $input, $offset );
89+
$this->assertNull( $result, 'Subsequent sibling combinator (~) should not be supported' );
90+
}
91+
92+
/**
93+
* @ticket 62653
94+
*/
95+
public function test_parse_complex_selector_with_multiple_combinators() {
96+
$input = 'div > ul li > a.link';
97+
$offset = 0;
98+
$result = WP_CSS_Complex_Selector::parse( $input, $offset );
99+
100+
$this->assertNotNull( $result );
101+
$this->assertSame( 3, count( $result->context_selectors ) );
102+
103+
$this->assertInstanceOf( WP_CSS_Compound_Selector::class, $result->self_selector );
104+
$this->assertSame( 'a', $result->self_selector->type_selector->type );
105+
$this->assertSame( 'link', $result->self_selector->subclass_selectors[0]->class_name );
106+
107+
// Check context selectors are in reverse order
108+
$this->assertSame( 3, count( $result->context_selectors ) );
109+
110+
$this->assertSame( 'li', $result->context_selectors[0][0]->type );
111+
$this->assertSame( WP_CSS_Complex_Selector::COMBINATOR_CHILD, $result->context_selectors[0][1] );
112+
113+
$this->assertSame( 'ul', $result->context_selectors[1][0]->type );
114+
$this->assertSame( WP_CSS_Complex_Selector::COMBINATOR_DESCENDANT, $result->context_selectors[1][1] );
115+
116+
$this->assertSame( 'div', $result->context_selectors[2][0]->type );
117+
$this->assertSame( WP_CSS_Complex_Selector::COMBINATOR_CHILD, $result->context_selectors[2][1] );
118+
}
119+
120+
/**
121+
* @ticket 62653
122+
*/
123+
public function test_parse_complex_selector_with_whitespace_variations() {
124+
$input = "div\n>\t\rul \f li\r\n>\na.link";
125+
$offset = 0;
126+
$result = WP_CSS_Complex_Selector::parse( $input, $offset );
127+
128+
$this->assertNotNull( $result );
129+
$this->assertSame( 3, count( $result->context_selectors ) );
130+
}
131+
132+
/**
133+
* @ticket 62653
134+
*/
135+
public function test_parse_invalid_trailing_combinator() {
136+
$input = 'div > ul >';
137+
$offset = 0;
138+
$result = WP_CSS_Complex_Selector::parse( $input, $offset );
139+
$this->assertNull( $result, 'Trailing combinator should make selector invalid' );
140+
}
71141
}

tests/phpunit/tests/html-api/wpCssComplexSelectorList.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,49 @@ public function test_parse_empty_selector_list() {
4848
$result = WP_CSS_Complex_Selector_List::from_selectors( $input );
4949
$this->assertNull( $result );
5050
}
51+
52+
/**
53+
* @ticket 62653
54+
*/
55+
public function test_parse_single_selector() {
56+
$input = 'div.class';
57+
$result = WP_CSS_Complex_Selector_List::from_selectors( $input );
58+
$this->assertNotNull( $result );
59+
}
60+
61+
/**
62+
* @ticket 62653
63+
*/
64+
public function test_parse_selector_list_with_whitespace() {
65+
$input = " div.class1 ,\n\t span#id2 , p[attr='value'] ";
66+
$result = WP_CSS_Complex_Selector_List::from_selectors( $input );
67+
$this->assertNotNull( $result );
68+
}
69+
70+
/**
71+
* @ticket 62653
72+
*/
73+
public function test_parse_selector_list_with_unsupported_sibling_combinator() {
74+
$input = 'div + p, span.class';
75+
$result = WP_CSS_Complex_Selector_List::from_selectors( $input );
76+
$this->assertNull( $result );
77+
}
78+
79+
/**
80+
* @ticket 62653
81+
*/
82+
public function test_parse_selector_list_with_trailing_comma() {
83+
$input = 'div.class,';
84+
$result = WP_CSS_Complex_Selector_List::from_selectors( $input );
85+
$this->assertNull( $result );
86+
}
87+
88+
/**
89+
* @ticket 62653
90+
*/
91+
public function test_parse_selector_list_with_leading_comma() {
92+
$input = ',div.class';
93+
$result = WP_CSS_Complex_Selector_List::from_selectors( $input );
94+
$this->assertNull( $result );
95+
}
5196
}

tests/phpunit/tests/html-api/wpCssCompoundSelector.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

tests/phpunit/tests/html-api/wpCssCompoundSelectorList.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,58 @@ public function test_unsupported_complex_selector() {
5757
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
5858
$this->assertNull( $result );
5959
}
60+
61+
/**
62+
* @ticket 62653
63+
*/
64+
public function test_parse_single_compound_selector() {
65+
$input = 'div.class#id[attr="value"]';
66+
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
67+
$this->assertNotNull( $result );
68+
}
69+
70+
/**
71+
* @ticket 62653
72+
*/
73+
public function test_parse_compound_selector_list_with_whitespace() {
74+
$input = " div.class1 ,\n\t span#id2 , p[attr='value'] ";
75+
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
76+
$this->assertNotNull( $result );
77+
}
78+
79+
/**
80+
* @ticket 62653
81+
*/
82+
public function test_parse_compound_selector_list_with_child_combinator() {
83+
$input = 'div > p, span.class';
84+
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
85+
$this->assertNull( $result );
86+
}
87+
88+
/**
89+
* @ticket 62653
90+
*/
91+
public function test_parse_compound_selector_list_with_sibling_combinator() {
92+
$input = 'div + p, span.class';
93+
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
94+
$this->assertNull( $result );
95+
}
96+
97+
/**
98+
* @ticket 62653
99+
*/
100+
public function test_parse_compound_selector_list_with_trailing_comma() {
101+
$input = 'div.class,';
102+
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
103+
$this->assertNull( $result );
104+
}
105+
106+
/**
107+
* @ticket 62653
108+
*/
109+
public function test_parse_compound_selector_list_with_leading_comma() {
110+
$input = ',div.class';
111+
$result = WP_CSS_Compound_Selector_List::from_selectors( $input );
112+
$this->assertNull( $result );
113+
}
60114
}

tests/phpunit/tests/html-api/wpCssIdSelector.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,21 @@ public static function data_id_selectors(): array {
4141
'escaped #\31 23' => array( '#\\31 23', '123', '' ),
4242
'with descendant #\31 23 div' => array( '#\\31 23 div', '123', ' div' ),
4343

44+
// Additional edge cases
45+
'multiple hashes #a#b' => array( '#a#b', 'a', '#b' ),
46+
'escaped hash #\\23 hash' => array( '#\\23 hash', '#hash', '' ),
47+
'unicode ID #café' => array( '#café', 'café', '' ),
48+
'hyphen ID #my-id' => array( '#my-id', 'my-id', '' ),
49+
'underscore ID #my_id' => array( '#my_id', 'my_id', '' ),
50+
'long ID name' => array( '#very-long-id-name-with-many-hyphens', 'very-long-id-name-with-many-hyphens', '' ),
51+
4452
// Invalid
4553
'not ID foo' => array( 'foo' ),
4654
'not ID .bar' => array( '.bar' ),
4755
'not valid #1foo' => array( '#1foo' ),
56+
'empty after hash' => array( '#' ),
57+
'space after hash' => array( '# ' ),
58+
'invalid after hash' => array( '#@invalid' ),
4859
);
4960
}
5061
}

0 commit comments

Comments
 (0)