"""Testcases for css_parser.css.selector.Selector.

what should happen here?
    - star 7 hack::
        x*
        does not validate but works in IE>5 and FF, does it???

"""
from __future__ import absolute_import
from __future__ import unicode_literals
import xml.dom
from . import basetest
import css_parser
import sys


class SelectorTestCase(basetest.BaseTestCase):

    def setUp(self):
        self.r = css_parser.css.Selector('*')

    def test_init(self):
        "Selector.__init__()"
        s = css_parser.css.Selector('*')
        self.assertEqual((None, '*'), s.element)
        self.assertEqual({}, s._namespaces.namespaces)
        self.assertEqual(None, s.parent)
        self.assertEqual('*', s.selectorText)
        self.assertEqual((0, 0, 0, 0), s.specificity)
        self.assertEqual(True, s.wellformed)

        s = css_parser.css.Selector(('p|b', {'p': 'URI'}))
        self.assertEqual(('URI', 'b'), s.element)
        self.assertEqual({'p': 'URI'}, s._namespaces.namespaces)
        self.assertEqual(None, s.parent)
        self.assertEqual('p|b', s.selectorText)
        self.assertEqual((0, 0, 0, 1), s.specificity)
        self.assertEqual(True, s.wellformed)

        self.assertRaisesEx(xml.dom.NamespaceErr, css_parser.css.Selector, 'p|b')

    def test_element(self):
        "Selector.element (TODO: RESOLVE)"
        tests = {
            '*': (None, '*'),
            'x': (None, 'x'),
            '\\x': (None, '\\x'),
            '|x': ('', 'x'),
            '*|x': (css_parser._ANYNS, 'x'),
            'ex|x': ('example', 'x'),
            'a x': (None, 'x'),
            'a+x': (None, 'x'),
            'a>x': (None, 'x'),
            'a~x': (None, 'x'),
            'a+b~c x': (None, 'x'),
            'x[href]': (None, 'x'),
            'x[href="123"]': (None, 'x'),
            'x:hover': (None, 'x'),
            'x:first-letter': (None, 'x'),  # TODO: Really?
            'x::first-line': (None, 'x'),  # TODO: Really?
            'x:not(href)': (None, 'x'),  # TODO: Really?

            '#id': None,
            '.c': None,
            'x#id': (None, 'x'),
            'x.c': (None, 'x')
        }
        for test, ele in tests.items():
            s = css_parser.css.Selector((test, {'ex': 'example'}))
            self.assertEqual(ele, s.element)

    def test_namespaces(self):
        "Selector.namespaces"
        namespaces = [
            {'p': 'other'},  # no default
            {'': 'default', 'p': 'other'},  # with default
            {'': 'default', 'p': 'default'}  # same default
        ]
        tests = {
            # selector: with default, no default, same default
            '*': ('*', '*', '*'),
            'x': ('x', 'x', 'x'),
            '|*': ('|*', '|*', '|*'),
            '|x': ('|x', '|x', '|x'),
            '*|*': ('*|*', '*|*', '*|*'),
            '*|x': ('*|x', '*|x', '*|x'),
            'p|*': ('p|*', 'p|*', '*'),
            'p|x': ('p|x', 'p|x', 'x'),
            'x[a][|a][*|a][p|a]': ('x[a][a][*|a][p|a]',
                                   'x[a][a][*|a][p|a]',
                                   'x[a][a][*|a][a]')
        }
        for sel, exp in tests.items():
            for i, result in enumerate(exp):
                s = css_parser.css.Selector((sel, namespaces[i]))
                self.assertEqual(result, s.selectorText)

        # add to CSSStyleSheet
        sheet = css_parser.css.CSSStyleSheet()
        sheet.cssText = '@namespace p "u"; a { color: green }'

        r = sheet.cssRules[1]

        self.assertEqual(r.selectorText, 'a')

        # add default namespace
        sheet.namespaces[''] = 'a'
        self.assertEqual(r.selectorText, '|a')

        del sheet.namespaces['']
        self.assertEqual(r.selectorText, 'a')

#        r.selectorList.append('a')
#        self.assertEqual(r.selectorText, u'|a, a')
#        r.selectorList.append('*|a')
#        self.assertEqual(r.selectorText, u'|a, a, *|a')

    def test_default_namespace(self):
        "Selector.namespaces default"
        css = '''@namespace "default";
                a[att] { color:green; }
        '''
        sheet = css_parser.css.CSSStyleSheet()
        sheet.cssText = css
        self.assertEqual(sheet.cssText,
                         '@namespace "default";\na[att] {\n    color: green\n    }'.encode())
        # use a prefix for default namespace, does not goes for atts!
        sheet.namespaces['p'] = 'default'
        self.assertEqual(sheet.cssText,
                         '@namespace p "default";\np|a[att] {\n    color: green\n    }'.encode())

    def test_parent(self):
        "Selector.parent"
        sl = css_parser.css.SelectorList('a, b')
        for sel in sl:
            self.assertEqual(sl, sel.parent)

        newsel = css_parser.css.Selector('x')
        sl.append(newsel)
        self.assertEqual(sl, newsel.parent)

        newsel = css_parser.css.Selector('y')
        sl.appendSelector(newsel)
        self.assertEqual(sl, newsel.parent)

    def test_selectorTextSimple(self):
        "Selector.selectorText"
        tests = {
            # combinators
            'a+b>c~e f': 'a + b > c ~ e f',
            'a  +  b  >  c  ~  e   f': 'a + b > c ~ e f',
            'a+b': 'a + b',
            'a  +  b': 'a + b',
            'a\n  +\t  b': 'a + b',
            'a~b': 'a ~ b',
            'a b': None,
            'a   b': 'a b',
            'a\nb': 'a b',
            'a\tb': 'a b',
            'a   #b': 'a #b',
            'a   .b': 'a .b',
            'a * b': None,
            # >
            'a>b': 'a > b',
            'a> b': 'a > b',
            'a >b': 'a > b',
            'a > b': 'a > b',
            # +
            'a+ b': 'a + b',
            'a +b': 'a + b',
            'a + b': 'a + b',
            # ~
            'a~ b': 'a ~ b',
            'a ~b': 'a ~ b',
            'a ~ b': 'a ~ b',

            # type selector
            'a': None,
            'h1-a_x__--': None,
            'a-a': None,
            'a_a': None,
            '-a': None,
            '_': None,
            '-_': None,
            r'-\72': '-r',
            # ur'\25': u'%', # TODO: should be escaped!
            '.a a': None,
            'a1': None,
            'a1-1': None,
            '.a1-1': None,

            # universal
            '*': None,
            '*/*x*/': None,
            '* /*x*/': None,
            '*:hover': None,
            '* :hover': None,
            '*:lang(fr)': None,
            '* :lang(fr)': None,
            '*::first-line': None,
            '* ::first-line': None,
            '*[lang=fr]': None,
            '[lang=fr]': None,

            # HASH
            '''#a''': None,
            '''#a1''': None,
            '''#1a''': None,  # valid to grammar but not for HTML
            '''#1''': None,  # valid to grammar but not for HTML
            '''a#b''': None,
            '''a #b''': None,
            '''a#b.c''': None,
            '''a.c#b''': None,
            '''a #b.c''': None,
            '''a .c#b''': None,

            # class
            'ab': 'ab',
            'a.b': None,
            'a.b.c': None,
            '.a1._1': None,

            # attrib
            '''[x]''': None,
            '''*[x]''': None,
            '''a[x]''': None,
            '''a[ x]''': 'a[x]',
            '''a[x ]''': 'a[x]',
            '''a [x]''': 'a [x]',
            '''* [x]''': None,  # is really * *[x]

            '''a[x="1"]''': None,
            '''a[x ="1"]''': 'a[x="1"]',
            '''a[x= "1"]''': 'a[x="1"]',
            '''a[x = "1"]''': 'a[x="1"]',
            '''a[ x = "1"]''': 'a[x="1"]',
            '''a[x = "1" ]''': 'a[x="1"]',
            '''a[ x = "1" ]''': 'a[x="1"]',
            '''a [ x = "1" ]''': 'a [x="1"]',

            '''a[x~=a1]''': None,
            '''a[x ~=a1]''': 'a[x~=a1]',
            '''a[x~= a1]''': 'a[x~=a1]',
            '''a[x ~= a1]''': 'a[x~=a1]',
            '''a[ x ~= a1]''': 'a[x~=a1]',
            '''a[x ~= a1 ]''': 'a[x~=a1]',
            '''a[ x ~= a1 ]''': 'a[x~=a1]',
            '''a [ x ~= a1 ]''': 'a [x~=a1]',  # same as next!
            '''a *[ x ~= a1 ]''': 'a *[x~=a1]',

            '''a[x|=en]''': None,
            '''a[x|= en]''': 'a[x|=en]',
            '''a[x |=en]''': 'a[x|=en]',
            '''a[x |= en]''': 'a[x|=en]',
            '''a[ x |= en]''': 'a[x|=en]',
            '''a[x |= en ]''': 'a[x|=en]',
            '''a [ x |= en]''': 'a [x|=en]',
            # CSS3
            '''a[x^=en]''': None,
            '''a[x$=en]''': None,
            '''a[x*=en]''': None,

            '''a[/*1*/x/*2*/]''': None,
            '''a[/*1*/x/*2*/=/*3*/a/*4*/]''': None,
            '''a[/*1*/x/*2*/~=/*3*/a/*4*/]''': None,
            '''a[/*1*/x/*2*/|=/*3*/a/*4*/]''': None,

            # pseudo-elements
            'a x:first-line': None,
            'a x:first-letter': None,
            'a x:before': None,
            'a x:after': None,
            'a x::selection': None,
            'a:hover+b:hover>c:hover~e:hover f:hover':
                'a:hover + b:hover > c:hover ~ e:hover f:hover',
            'a:hover  +  b:hover  >  c:hover  ~  e:hover   f:hover':
                'a:hover + b:hover > c:hover ~ e:hover f:hover',
            'a::selection+b::selection>c::selection~e::selection f::selection':
                'a::selection + b::selection > c::selection ~ e::selection f::selection',
            'a::selection  +  b::selection  >  c::selection  ~  e::selection   f::selection':
                'a::selection + b::selection > c::selection ~ e::selection f::selection',

            'x:lang(de) y': None,
            'x:nth-child(odd) y': None,
            # functional pseudo
            'x:func(a + b-2px22.3"s"i)': None,
            'x:func(1 + 1)': None,
            'x:func(1+1)': 'x:func(1+1)',
            'x:func(1   +   1)': 'x:func(1 + 1)',
            'x:func(1-1)': 'x:func(1-1)',
            'x:func(1  -  1)': 'x:func(1 -1)',  # TODO: FIX!
            'x:func(a-1)': 'x:func(a-1)',
            'x:func(a -1px)': 'x:func(a -1px)',
            'x:func(1px)': None,
            'x:func(23.4)': None,
            'x:func("s")': None,
            'x:func(i)': None,

            # negation
            ':not(y)': None,
            ':not(   y  \t\n)': ':not(y)',
            '*:not(y)': None,
            'x:not(y)': None,
            '.x:not(y)': None,
            ':not(*)': None,
            ':not(#a)': None,
            ':not(.a)': None,
            ':not([a])': None,
            ':not(:first-letter)': None,
            ':not(::first-letter)': None,

            # escapes
            r'\74\72 td': 'trtd',
            r'\74\72  td': 'tr td',
            r'\74\000072 td': 'trtd',
            r'\74\000072  td': 'tr td',

            # comments
            'a/**/ b': None,
            'a /**/b': None,
            'a /**/ b': None,
            'a  /**/ b': 'a /**/ b',
            'a /**/  b': 'a /**/ b',

            # namespaces
            '|e': None,
            '*|e': None,
            '*|*': None,
            ('p|*', (('p', 'uri'),)): 'p|*',
            ('p|e', (('p', 'uri'),)): 'p|e',
            ('-a_x12|e', (('-a_x12', 'uri'),)): '-a_x12|e',
            ('*|b[p|a]', (('p', 'uri'),)): '*|b[p|a]',

            # case
            'elemenT.clasS#iD[atT="valuE"]:noT(x)::firsT-linE':
                'elemenT.clasS#iD[atT="valuE"]:not(x)::first-line'
        }
        # do not parse as not complete
        self.do_equal_r(tests, att='selectorText')

        tests = {
            'x|a': xml.dom.NamespaceErr,
            ('p|*', (('x', 'uri'),)): xml.dom.NamespaceErr,

            '': xml.dom.SyntaxErr,
            '1': xml.dom.SyntaxErr,
            '-1': xml.dom.SyntaxErr,
            'a*b': xml.dom.SyntaxErr,
            'a *b': xml.dom.SyntaxErr,
            'a* b': xml.dom.SyntaxErr,
            'a/**/b': xml.dom.SyntaxErr,

            '#': xml.dom.SyntaxErr,
            '|': xml.dom.SyntaxErr,

            ':': xml.dom.SyntaxErr,
            '::': xml.dom.SyntaxErr,
            ': a': xml.dom.SyntaxErr,
            ':: a': xml.dom.SyntaxErr,
            ':a()': xml.dom.SyntaxErr,  # no value
            '::a()': xml.dom.SyntaxErr,  # no value
            ':::a': xml.dom.SyntaxErr,
            ':1': xml.dom.SyntaxErr,

            '#.x': xml.dom.SyntaxErr,
            '.': xml.dom.SyntaxErr,
            '.1': xml.dom.SyntaxErr,
            '.a.1': xml.dom.SyntaxErr,

            '[a': xml.dom.SyntaxErr,
            'a]': xml.dom.SyntaxErr,
            '[a b]': xml.dom.SyntaxErr,
            '[=b]': xml.dom.SyntaxErr,
            '[a=]': xml.dom.SyntaxErr,
            '[a|=]': xml.dom.SyntaxErr,
            '[a~=]': xml.dom.SyntaxErr,
            '[a=1]': xml.dom.SyntaxErr,

            'a +': xml.dom.SyntaxErr,
            'a >': xml.dom.SyntaxErr,
            'a ++ b': xml.dom.SyntaxErr,
            'a + > b': xml.dom.SyntaxErr,

            # functional pseudo
            '*:lang(': xml.dom.SyntaxErr,
            '*:lang()': xml.dom.SyntaxErr,  # no arg

            # negation
            'not(x)': xml.dom.SyntaxErr,  # no valid function
            ':not()': xml.dom.SyntaxErr,  # no arg
            ':not(x': xml.dom.SyntaxErr,  # no )
            ':not(-': xml.dom.SyntaxErr,  # not allowed
            ':not(+': xml.dom.SyntaxErr,  # not allowed

            # only one selector!
            ',': xml.dom.InvalidModificationErr,
            ',a': xml.dom.InvalidModificationErr,
            'a,': xml.dom.InvalidModificationErr,

            # @
            'p @here': xml.dom.SyntaxErr,  # not allowed

        }
        # only set as not complete
        self.do_raise_r(tests, att='_setSelectorText')

    def test_specificity(self):
        "Selector.specificity"
        selector = css_parser.css.Selector()

        # readonly
        def _set(): selector.specificity = 1

        pattern = "object has no setter" if sys.version_info >= (3,11) else "can't set attribute"
        self.assertRaisesMsgSubstring(AttributeError, pattern, _set)

        tests = {
            '*': (0, 0, 0, 0),
            'li': (0, 0, 0, 1),
            'li:first-line': (0, 0, 0, 2),
            'ul li': (0, 0, 0, 2),
            'ul ol+li': (0, 0, 0, 3),
            'h1 + *[rel=up]': (0, 0, 1, 1),
            'ul ol li.red': (0, 0, 1, 3),
            'li.red.level': (0, 0, 2, 1),
            '#x34y': (0, 1, 0, 0),

            'UL OL LI.red': (0, 0, 1, 3),
            'LI.red.level': (0, 0, 2, 1),
            '#s12:not(FOO)': (0, 1, 0, 1),
            'button:not([DISABLED])': (0, 0, 1, 1),  # ?
            '*:not(FOO)': (0, 0, 0, 1),

            # elements
            'a+b': (0, 0, 0, 2),
            'a>b': (0, 0, 0, 2),
            'a b': (0, 0, 0, 2),
            '* a': (0, 0, 0, 1),
            'a *': (0, 0, 0, 1),
            'a * b': (0, 0, 0, 2),

            'a:hover': (0, 0, 1, 1),
            'a:first-line': (0, 0, 0, 2),
            'a:first-letter': (0, 0, 0, 2),
            'a:before': (0, 0, 0, 2),
            'a:after': (0, 0, 0, 2),

            # pseudo-classes count as class selectors (column c)
            ':last-child': (0, 0, 1, 0),
            'tr:last-child': (0, 0, 1, 1),
            ':nth-child(2)': (0, 0, 1, 0),
            '.table > :last-child > tr:last-child > *': (0, 0, 3, 1),

            # classes and attributes
            '.a': (0, 0, 1, 0),
            '*.a': (0, 0, 1, 0),
            'a.a': (0, 0, 1, 1),
            '.a.a': (0, 0, 2, 0),  # IE<7 False (0,0,1,0)
            'a.a.a': (0, 0, 2, 1),
            '.a.b': (0, 0, 2, 0),
            'a.a.b': (0, 0, 2, 1),
            '.a .a': (0, 0, 2, 0),
            '*[x]': (0, 0, 1, 0),
            '*[x=a]': (0, 0, 1, 0),
            '*[x~=a]': (0, 0, 1, 0),
            '*[x|=a]': (0, 0, 1, 0),
            '*[x^=a]': (0, 0, 1, 0),
            '*[x*=a]': (0, 0, 1, 0),
            '*[x$=a]': (0, 0, 1, 0),
            '*[x][y]': (0, 0, 2, 0),

            # ids
            '#a': (0, 1, 0, 0),
            '*#a': (0, 1, 0, 0),
            'x#a': (0, 1, 0, 1),
            '.x#a': (0, 1, 1, 0),
            'a.x#a': (0, 1, 1, 1),
            '#a#a': (0, 2, 0, 0),  # e.g. html:id + xml:id
            '#a#b': (0, 2, 0, 0),
            '#a #b': (0, 2, 0, 0),

            # pseudo classes
            'p:only-of-type': (0, 0, 1, 1),
            ':where(p)': (0, 0, 0, 0),
            ':where(p) span': (0, 0, 0, 1),
        }
        for text in tests:
            selector.selectorText = text
            self.assertEqual(tests[text], selector.specificity, text)

    def test_reprANDstr(self):
        "Selector.__repr__(), .__str__()"
        sel = 'a + b'

        s = css_parser.css.Selector(selectorText=sel)

        self.assertIn(sel, str(s))

        s2 = eval(repr(s))
        self.assertIsInstance(s2, s.__class__)
        self.assertEqual(sel, s2.selectorText)

    def test_selector_level4_is(self):
        "Selector :is() - CSS Selectors Level 4"
        tests = {
            # simple selector list
            ':is(a)': None,
            ':is(a, b)': None,
            ':is(a, b, c)': None,
            # complex selectors inside :is()
            ':is(a > b)': None,
            ':is(a b)': None,
            ':is(a + b)': None,
            ':is(a ~ b)': None,
            ':is(.a .b)': None,
            ':is(.a > .b)': None,
            ':is(a > b, c > d)': None,
            ':is(a b, c > d)': None,
            # combined with other selectors
            'a:is(.b, .c)': None,
            ':is(a):hover': None,
            ':is(h1, h2, h3):first-child': None,
            'div:is(.a, .b) span': None,
            ':is(.a) > :is(.b)': None,
            # nested :is()
            ':is(:is(a, b), c)': None,
            # with attributes
            ':is([type="text"], [type="email"])': None,
            # with pseudo-classes
            ':is(:hover, :focus)': None,
            # universal selector
            ':is(*)': None,
            # with id
            ':is(#a, #b)': None,
            # whitespace handling
            ':is( a , b )': ':is(a, b)',
            ':is(  a  >  b  )': ':is(a > b)',
        }
        self.do_equal_r(tests, att='selectorText')

    def test_selector_level4_where(self):
        "Selector :where() - CSS Selectors Level 4"
        tests = {
            ':where(a)': None,
            ':where(a, b)': None,
            ':where(a > b)': None,
            ':where(.a .b)': None,
            ':where(a, b, c)': None,
            ':where(a > b, c ~ d)': None,
            'div:where(.x, .y) span': None,
            ':where(:hover, :focus)': None,
            ':where( a , b )': ':where(a, b)',
        }
        self.do_equal_r(tests, att='selectorText')

    def test_selector_level4_not(self):
        "Selector :not() - CSS Selectors Level 4 (complex selectors)"
        tests = {
            # Level 3 simple cases still work
            ':not(y)': None,
            ':not(*)': None,
            ':not(#a)': None,
            ':not(.a)': None,
            ':not([a])': None,
            ':not(:hover)': None,
            # Level 4: selector lists
            ':not(a, b)': None,
            ':not(.a, .b)': None,
            ':not(#a, #b)': None,
            # Level 4: complex selectors
            ':not(a > b)': None,
            ':not(a b)': None,
            ':not(.a .b)': None,
            ':not(a > b, c > d)': None,
            # combined with other selectors
            'a:not(.b, .c)': None,
            ':not(.disabled):hover': None,
            ':not( a , b )': ':not(a, b)',
        }
        self.do_equal_r(tests, att='selectorText')

    def test_selector_level4_has(self):
        "Selector :has() - CSS Selectors Level 4"
        tests = {
            # basic :has() with descendant
            ':has(.icon)': None,
            ':has(span)': None,
            ':has(#id)': None,
            ':has([attr])': None,
            # complex selectors
            ':has(.a > .b)': None,
            ':has(.a .b)': None,
            ':has(.a + .b)': None,
            ':has(.a ~ .b)': None,
            # relative selectors (leading combinators)
            ':has(> .icon)': None,
            ':has(+ .sibling)': None,
            ':has(~ .sibling)': None,
            ':has(> .a, > .b)': None,
            ':has(> .a, .b)': None,
            # selector list
            ':has(.a, .b)': None,
            ':has(span, .icon)': None,
            # combined with other selectors
            'a:has(> img)': None,
            'section:has(h1, h2, h3)': None,
            ':has(.a):hover': None,
            ':has( > .icon )': ':has(> .icon)',
        }
        self.do_equal_r(tests, att='selectorText')

    def test_selector_level4_slotted(self):
        "Selector ::slotted() - compound selector argument"
        tests = {
            '::slotted(span)': None,
            '::slotted(.cls)': None,
            '::slotted(#id)': None,
            '::slotted([attr])': None,
            '::slotted(span.cls)': None,
            '::slotted(div.a.b)': None,
            '::slotted(*)': None,
        }
        self.do_equal_r(tests, att='selectorText')

    def test_selector_level4_specificity(self):
        "Selector specificity for Level 4 pseudo-classes"
        selector = css_parser.css.Selector()
        tests = {
            # :is() takes specificity of most specific argument
            ':is(a)': (0, 0, 0, 1),
            ':is(a, b)': (0, 0, 0, 1),
            ':is(.a)': (0, 0, 1, 0),
            ':is(#id)': (0, 1, 0, 0),
            ':is(#id, .cls)': (0, 1, 0, 0),
            ':is(a, b.c)': (0, 0, 1, 1),
            ':is(a > b)': (0, 0, 0, 2),
            'a:is(.b, .c)': (0, 0, 1, 1),

            # :where() always has 0 specificity
            ':where(a)': (0, 0, 0, 0),
            ':where(#id)': (0, 0, 0, 0),
            ':where(#id, .cls)': (0, 0, 0, 0),
            ':where(.a .b)': (0, 0, 0, 0),
            ':where(p) span': (0, 0, 0, 1),
            'div:where(.a) span': (0, 0, 0, 2),

            # :not() takes specificity of most specific argument
            ':not(a)': (0, 0, 0, 1),
            ':not(.a)': (0, 0, 1, 0),
            ':not(#id)': (0, 1, 0, 0),
            ':not(a, b)': (0, 0, 0, 1),
            ':not(.a, .b)': (0, 0, 1, 0),
            ':not(#id, .cls)': (0, 1, 0, 0),

            # :has() takes specificity of most specific argument
            ':has(.icon)': (0, 0, 1, 0),
            ':has(#id)': (0, 1, 0, 0),
            ':has(> .icon)': (0, 0, 1, 0),
            ':has(.a > .b)': (0, 0, 2, 0),
            ':has(.a, #id)': (0, 1, 0, 0),

            # ::slotted() = pseudo-element + argument specificity
            '::slotted(span)': (0, 0, 0, 2),
            '::slotted(.cls)': (0, 0, 1, 1),
            '::slotted(#id)': (0, 1, 0, 1),
            '::slotted(span.cls)': (0, 0, 1, 2),
        }
        for text, expected in tests.items():
            selector.selectorText = text
            self.assertEqual(expected, selector.specificity, text)

    def test_selector_level4_invalid(self):
        "Selector Level 4 invalid selectors"
        import xml.dom
        tests = {
            # empty arguments
            ':is()': xml.dom.SyntaxErr,
            ':where()': xml.dom.SyntaxErr,
            ':not()': xml.dom.SyntaxErr,
            ':has()': xml.dom.SyntaxErr,
            # missing closing paren
            ':is(a': xml.dom.SyntaxErr,
            ':not(a': xml.dom.SyntaxErr,
            ':has(a': xml.dom.SyntaxErr,
            ':where(a': xml.dom.SyntaxErr,
            # invalid content
            ':is(,)': xml.dom.SyntaxErr,
            ':is(a,)': xml.dom.SyntaxErr,
            ':has(,)': xml.dom.SyntaxErr,
        }
        self.do_raise_r(tests, att='_setSelectorText')

    def test_selector_level4_nesting(self):
        "Selector Level 4 nested pseudo-classes"
        tests = {
            ':is(:not(a))': None,
            ':not(:is(a, b))': None,
            ':is(:where(a, b), c)': None,
            ':has(:is(.a, .b))': None,
            ':is(:is(:is(a)))': None,
        }
        self.do_equal_r(tests, att='selectorText')

    def test_selector_level4_complex_combinations(self):
        "Selector Level 4 complex selector combinations"
        tests = {
            # Multiple Level 4 pseudo-classes in one selector
            ':is(a):not(.disabled)': None,
            ':is(h1, h2):where(.title)': None,
            # With descendant/child combinators
            ':is(article, section) > :is(h1, h2)': None,
            'div:has(> img) + p': None,
            # Practical patterns
            ':is(ol, ul) :is(ol, ul) li': None,
            'a:not(:hover):not(:focus)': None,
        }
        self.do_equal_r(tests, att='selectorText')


if __name__ == '__main__':
    import unittest
    unittest.main()
