@@ -146,6 +146,20 @@ def parse_many(first, *others):
146146 'Negation[Element[div]:not(Class[Element[div].foo])]' ]
147147 assert parse_many ('td ~ th' ) == [
148148 'CombinedSelector[Element[td] ~ Element[th]]' ]
149+ assert parse_many (':scope > foo' ) == [
150+ 'CombinedSelector[Pseudo[Element[*]:scope] > Element[foo]]'
151+ ]
152+ assert parse_many (' :scope > foo' ) == [
153+ 'CombinedSelector[Pseudo[Element[*]:scope] > Element[foo]]'
154+ ]
155+ assert parse_many (':scope > foo bar > div' ) == [
156+ 'CombinedSelector[CombinedSelector[CombinedSelector[Pseudo[Element[*]:scope] > '
157+ 'Element[foo]] <followed> Element[bar]] > Element[div]]'
158+ ]
159+ assert parse_many (':scope > #foo #bar' ) == [
160+ 'CombinedSelector[CombinedSelector[Pseudo[Element[*]:scope] > '
161+ 'Hash[Element[*]#foo]] <followed> Hash[Element[*]#bar]]'
162+ ]
149163
150164 def test_pseudo_elements (self ):
151165 def parse_pseudo (css ):
@@ -164,9 +178,16 @@ def parse_one(css):
164178 assert len (result ) == 1
165179 return result [0 ]
166180
181+ def test_pseudo_repr (css ):
182+ result = parse (css )
183+ assert len (result ) == 1
184+ selector = result [0 ]
185+ return selector .parsed_tree .__repr__ ()
186+
167187 assert parse_one ('foo' ) == ('Element[foo]' , None )
168188 assert parse_one ('*' ) == ('Element[*]' , None )
169189 assert parse_one (':empty' ) == ('Pseudo[Element[*]:empty]' , None )
190+ assert parse_one (':scope' ) == ('Pseudo[Element[*]:scope]' , None )
170191
171192 # Special cases for CSS 2.1 pseudo-elements
172193 assert parse_one (':BEfore' ) == ('Element[*]' , 'before' )
@@ -190,11 +211,14 @@ def parse_one(css):
190211 'CombinedSelector[Hash[Element[lorem]#ipsum] ~ '
191212 'Pseudo[Attrib[Class[Hash[Element[a]#b].c][href]]:empty]]' ,
192213 'selection' )
193-
194- parse_pseudo ('foo:before, bar, baz:after' ) == [
195- ('Element[foo]' , 'before' ),
196- ('Element[bar]' , None ),
197- ('Element[baz]' , 'after' )]
214+ assert parse_pseudo (':scope > div, foo bar' ) == [
215+ ('CombinedSelector[Pseudo[Element[*]:scope] > Element[div]]' , None ),
216+ ('CombinedSelector[Element[foo] <followed> Element[bar]]' , None )
217+ ]
218+ assert parse_pseudo ('foo:before, bar, baz:after' ) == [
219+ ('Element[foo]' , 'before' ), ('Element[bar]' , None ),
220+ ('Element[baz]' , 'after' )
221+ ]
198222
199223 # Special cases for CSS 2.1 pseudo-elements are ignored by default
200224 for pseudo in ('after' , 'before' , 'first-line' , 'first-letter' ):
@@ -211,6 +235,11 @@ def parse_one(css):
211235 self .assertRaises (ExpressionError , tr .selector_to_xpath , selector ,
212236 translate_pseudo_elements = True )
213237
238+ # Special test for the unicode symbols and ':scope' element if check
239+ # Errors if use repr() instead of __repr__()
240+ assert test_pseudo_repr (u':fİrst-child' ) == u'Pseudo[Element[*]:fİrst-child]'
241+ assert test_pseudo_repr (':scope' ) == 'Pseudo[Element[*]:scope]'
242+
214243 def test_specificity (self ):
215244 def specificity (css ):
216245 selectors = parse (css )
@@ -310,6 +339,13 @@ def get_error(css):
310339 "Got pseudo-element ::before inside :not() at 12" )
311340 assert get_error (':not(:not(a))' ) == (
312341 "Got nested :not()" )
342+ assert get_error (':scope > div :scope header' ) == (
343+ 'Got immediate child pseudo-element ":scope" not at the start of a selector'
344+ )
345+ assert get_error ('div :scope header' ) == (
346+ 'Got immediate child pseudo-element ":scope" not at the start of a selector'
347+ )
348+ assert get_error ('> div p' ) == ("Expected selector, got <DELIM '>' at 0>" )
313349
314350 def test_translation (self ):
315351 def xpath (css ):
@@ -483,6 +519,8 @@ def test_quoting(self):
483519 '''descendant-or-self::*[@aval = '"']''' )
484520 assert css_to_xpath ('*[aval=\' """\' ]' ) == (
485521 '''descendant-or-self::*[@aval = '"""']''' )
522+ assert css_to_xpath (':scope > div[dataimg="<testmessage>"]' ) == (
523+ "descendant-or-self::*[1]/div[@dataimg = '<testmessage>']" )
486524
487525 def test_unicode_escapes (self ):
488526 # \22 == '"' \20 == ' '
@@ -560,6 +598,7 @@ def xpath(css):
560598 assert xpath ('::attr-href' ) == "descendant-or-self::*/@href"
561599 assert xpath ('p img::attr(src)' ) == (
562600 "descendant-or-self::p/descendant-or-self::*/img/@src" )
601+ assert xpath (':scope' ) == "descendant-or-self::*[1]"
563602
564603 def test_series (self ):
565604 def series (css ):
@@ -672,6 +711,11 @@ def pcss(main, *selectors, **kwargs):
672711 assert pcss (':lang("EN")' , '*:lang(en-US)' , html_only = True ) == [
673712 'second-li' , 'li-div' ]
674713 assert pcss (':lang("e")' , html_only = True ) == []
714+ assert pcss (':scope > div' ) == []
715+ assert pcss (':scope body' ) == ['nil' ]
716+ assert pcss (':scope body > div' ) == ['outer-div' , 'foobar-div' ]
717+ assert pcss (':scope head' ) == ['nil' ]
718+ assert pcss (':scope html' ) == []
675719
676720 # --- nth-* and nth-last-* -------------------------------------
677721
@@ -853,6 +897,9 @@ def count(selector):
853897 assert count ('div[class|=dialog]' ) == 50 # ? Seems right
854898 assert count ('div[class!=madeup]' ) == 243 # ? Seems right
855899 assert count ('div[class~=dialog]' ) == 51 # ? Seems right
900+ assert count (':scope > div' ) == 1
901+ assert count (':scope > div > div[class=dialog]' ) == 1
902+ assert count (':scope > div div' ) == 242
856903
857904XMLLANG_IDS = '''
858905<test>
0 commit comments