"""Testcases for cssutils.css.CSSValue and CSSPrimitiveValue.""" import re import pytest import cssutils CSS2 = (cssutils.profile.CSS_LEVEL_2,) C3BUI = (cssutils.profile.CSS3_BASIC_USER_INTERFACE,) C3BB = (cssutils.profile.CSS3_BACKGROUNDS_AND_BORDERS,) CM3 = (cssutils.profile.CSS3_COLOR,) FM3 = (cssutils.profile.CSS3_FONTS,) C3PM = (cssutils.profile.CSS3_PAGED_MEDIA,) C3T = (cssutils.profile.CSS3_TEXT,) FM3FF = (cssutils.profile.CSS3_FONT_FACE,) CSS2_CM3 = (CM3[0], CSS2[0]) CSS2_FM3 = (FM3[0], CSS2[0]) class TestProfiles: M1 = {'testvalue': 'x'} P1 = { '-test-tokenmacro': '({num}{w}){1,2}', '-test-macro': '{ident}|{percentage}', '-test-custommacro': '{testvalue}', # custom validation function '-test-funcval': lambda v: int(v) > 0, } def test_knownNames(self): "Profiles.knownNames" p = cssutils.profiles.Profiles() p.removeProfile(all=True) p.addProfile('test', self.P1, self.M1) assert p.knownNames == list(self.P1.keys()) p.removeProfile(all=True) assert p.knownNames == [] def test_profiles(self): "Profiles.profiles" p = cssutils.profiles.Profiles() p.removeProfile(all=True) p.addProfile('test', self.P1, self.M1) assert p.profiles == ['test'] p.removeProfile(all=True) assert p.profiles == [] def test_validate2(self): "Profiles.validate()" # save saved = cssutils.profile # test p = cssutils.profiles.Profiles() cssutils.profile = p pvs = [('color', 'red'), ('color', 'rgba(0,0,0,0)'), ('color', 'XXX')] def check(*results): for i, pv in enumerate(pvs): assert p.validate(*pv) == results[i] check(True, True, False) p.removeProfile(p.CSS3_COLOR) check(True, False, False) cssutils.profile.addProfile('test', {}, {'color': 'XXX'}) check(False, False, True) p.removeProfile(all=True) check(False, False, False) # TODO: validateWithProfile # restore cssutils.profile = saved @pytest.mark.usefixtures('saved_profiles') def test_addProfile(self): "Profiles.addProfile with custom validation function" # unknown profile with pytest.raises(cssutils.profiles.NoSuchProfileException): list(cssutils.profile.propertiesByProfile('NOTSET')) # new profile cssutils.profile.addProfile('test', self.P1, self.M1) props = list(self.P1.keys()) props.sort() assert props == list(cssutils.profile.propertiesByProfile('test')) cssutils.log.raiseExceptions = False tests = { ('-test-tokenmacro', '1'): True, ('-test-tokenmacro', '1 -2'): True, ('-test-tokenmacro', '1 2 3'): False, ('-test-tokenmacro', 'a'): False, ('-test-macro', 'a'): True, ('-test-macro', '0.1%'): True, ('-test-custommacro', 'x'): True, ('-test-custommacro', '1'): False, ('-test-custommacro', 'y'): False, ('-test-funcval', '1'): True, ('-test-funcval', '-1'): False, ('-test-funcval', 'x'): False, } for test, v in list(tests.items()): assert v == cssutils.profile.validate(*test) assert (v, v, ['test']) == cssutils.profile.validateWithProfile(*test) cssutils.log.raiseExceptions = True expmsg = re.escape("invalid literal for int() with base 10: 'x'") with pytest.raises(Exception, match=expmsg): cssutils.profile.validate('-test-funcval', 'x') def test_removeProfile(self): "Profiles.removeProfile()" p = cssutils.profiles.Profiles() assert 9 == len(p.profiles) p.removeProfile(p.CSS_LEVEL_2) assert 8 == len(p.profiles) p.removeProfile(all=True) assert 0 == len(p.profiles) def _gen_validation_inputs(): CSS_LEVEL_2 = cssutils.profiles.Profiles.CSS_LEVEL_2 CSS3_COLOR = cssutils.profiles.Profiles.CSS3_COLOR yield ( ('color', 'red', None), (True, True, [CSS_LEVEL_2]), ) yield ( ('color', 'red', CSS_LEVEL_2), (True, True, [CSS_LEVEL_2]), ) yield ( ('color', 'red', CSS3_COLOR), (True, False, [CSS_LEVEL_2]), ) yield pytest.param( ('color', 'rgba(0,0,0,0)', None), (True, True, [CSS3_COLOR]), marks=pytest.mark.xfail(reason="#37"), ) yield pytest.param( ('color', 'rgba(0,0,0,0)', CSS_LEVEL_2), (True, False, [CSS3_COLOR]), marks=pytest.mark.xfail(reason="#37"), ) yield pytest.param( ('color', 'rgba(0,0,0,0)', CSS3_COLOR), (True, True, [CSS3_COLOR]), marks=pytest.mark.xfail(reason="#37"), ) yield pytest.param( ('color', '1px', None), ( False, False, [CSS3_COLOR, CSS_LEVEL_2], ), marks=pytest.mark.xfail(reason="#37"), ) yield pytest.param( ('color', '1px', CSS_LEVEL_2), ( False, False, [CSS3_COLOR, CSS_LEVEL_2], ), marks=pytest.mark.xfail(reason="#37"), ) yield pytest.param( ('color', '1px', CSS3_COLOR), ( False, False, [CSS3_COLOR, CSS_LEVEL_2], ), marks=pytest.mark.xfail(reason="#37"), ) yield ( ('color', 'aliceblue', None), (True, True, [CSS_LEVEL_2]), ) yield ( ('opacity', '1', None), (True, True, [CSS3_COLOR]), ) yield ( ('opacity', '1', CSS_LEVEL_2), (True, False, [CSS3_COLOR]), ) yield ( ('opacity', '1', CSS3_COLOR), (True, True, [CSS3_COLOR]), ) yield ( ('opacity', '1px', None), (False, False, [CSS3_COLOR]), ) yield ( ('opacity', '1px', CSS_LEVEL_2), (False, False, [CSS3_COLOR]), ) yield ( ('opacity', '1px', CSS3_COLOR), (False, False, [CSS3_COLOR]), ) yield ( ('-x', '1', None), (False, False, []), ) yield ( ('-x', '1', CSS_LEVEL_2), (False, False, []), ) yield ( ('-x', '1', CSS3_COLOR), (False, False, []), ) @pytest.mark.parametrize(('params', 'results'), _gen_validation_inputs()) def test_validateWithProfile(self, params, results): "Profiles.validate(), Profiles.validateWithProfile()" p = cssutils.profiles.Profiles() assert p.validate(*params[:2]) == results[0] assert p.validateWithProfile(*params) == results def test_propertiesByProfile(self): "Profiles.propertiesByProfile" assert ['opacity'] == list( cssutils.profile.propertiesByProfile(cssutils.profile.CSS3_COLOR) ) def test_csscolorlevel3(self): "CSS Color Module Level 3" # (propname, propvalue): (valid, validprofile) namedcolors = '''transparent, orange, aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white, yellow''' for color in namedcolors.split(','): color = color.strip() assert cssutils.profile.validate('color', color) assert (True, True, list(CSS2)) == cssutils.profile.validateWithProfile( 'color', color ) # CSS2 only: uicolor = ( 'ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|' 'ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|' 'HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|' 'InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|' 'ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|' 'WindowFrame|WindowText' ) for color in uicolor.split('|'): assert not cssutils.profile.validate('color', color) # TODO: fixme # assert (True, True, list(CSS2)) == cssutils.profile.validateWithProfile( # 'color', color # ) def test_validate(self): "Profiles.validate()" tests = { # name, values: valid, matching, profile # background-position ( 'background-position', ( 'inherit', '0', '1%', '1px', '0 0', '1% 1%', '1px 1px', '1px 1%', 'top', 'bottom', 'left', 'right', 'center center', 'center', 'top left', 'top center', 'top right', 'bottom left', 'bottom center', 'bottom right', 'center left', 'center center', 'center right', '0 center', 'center 0', '0 top', '10% bottom', 'left 0', 'right 10%', '1% center', 'center 1%', ), ): (True, True, CSS2), ('background-position', ('0 left', 'top 0')): (False, False, CSS2), ( 'border-top-right-radius', ( '1px', '1%', '1% -1px', '1% 0', ), ): (True, True, C3BB), ('border-top-right-radius', ('1px 2px 2px', '/ 1px', 'black')): ( False, False, C3BB, ), ( 'border-radius', ( '1px', '1%', '0', '1px 1px', '1px/ 1px', '1px /1px', '1px / 1px', '1px 1px 1px 1px', '1px 1px 1px 1px / 1px 1px 1px 1px', ), ): (True, True, C3BB), ( 'border-radius', ( '1px /', '/ 1px', '1px / 1px / 1px', '1px 1px 1px 1px 1px', '1px / 1px 1px 1px 1px 1px', 'black', ), ): (False, False, C3BB), ( 'border', ( '1px', 'solid', 'red', '1px solid red', '1px red solid', 'red 1px solid', 'red solid 1px', 'solid 1px red', 'solid red 1px', ), ): (True, True, C3BB), ( 'border', ( '1px 1px', 'red red 1px', ), ): (False, False, C3BB), ( 'box-shadow', ( 'none', '1px 1px', '1px 1px 1px', '1px 1px 1px 1px', '1px 1px 1px 1px red', 'inset 1px 1px', 'inset 1px 1px 1px 1px black', ), ): (True, True, C3BB), ( 'box-shadow', ( '1px', '1px 1px 1px 1px 1px', 'x 1px 1px', 'inset', '1px black', 'black', ), ): (False, False, C3BB), # color ( 'color', ( 'x', '#', '#0', '#00', '#0000', '#00000', '#0000000', '#00j', '#j00000', 'rgb(0.0,1,1)', 'rgb(0)', 'rgb(0, 1)', 'rgb(0, 1, 1, 1)', 'rgb(0, 1, 0%)', 'rgba(0)', 'rgba(0, 1, 1.0, 1)', 'rgba(0, 1)', 'rgba(0, 1, 1, 1, 1)', 'rgba(100%, 0%, 0%, 1%)', 'rgba(100%, 0%, 0, 1)', 'hsl(1.5,1%,1%)', 'hsl(1,1,1%)', 'hsl(1,1%,1)', 'hsla(1.5,1%,1%, 1)', 'hsla(1,1,1%, 1)', 'hsla(1,1%,1, 1)', 'hsla(1,1%,1%, 1%)', ), ): (False, False, CSS2_CM3), ( 'color', ( 'inherit', 'black', '#000', '#000000', 'rgb(0,1,1)', 'rgb( 0 , 1 , 1 )', 'rgb(-10,555,1)', 'rgb(100%, 1.5%, 0%)', 'rgb(150%, -20%, 0%)', ), ): (True, True, CSS2), ( 'color', ( 'currentcolor', 'aliceblue', 'rgba(1,1,1,1)', 'rgba( 1 , 1 , 1 , 1 )', 'rgba(100%, 0%, 0%, 1)', 'hsl(1,1%,1%)', 'hsl( 1 , 1% , 1% )', 'hsl(-1000,555.5%,-61.5%)', 'hsla(1,1%,1%,1)', 'hsla( 1, 1% , 1% , 1 )', 'hsla(-1000,555.5%,-61.5%, 0.5)', ), ): (True, True, CM3), ( 'content', ( 'none', 'normal', '""', "'x'", ), ): (True, True, CSS2), ( 'cursor', ('url(1), auto', 'url(1) 2 3, help', 'wait', 'inherit', 'none'), ): (True, True, C3BUI), ('cursor', ('url(1), auto, wait', 'url(1) 2, help', '1')): ( False, False, C3BUI, ), # FONTS ('font-family', ('serif, x',)): (True, True, CSS2), # CSS2_FM3), ( 'font-family', ( 'inherit', 'a, b', 'a,b,c', 'a, "b", c', '"a", b, "c"', '"a", "b", "c"', '"x y"', 'serif', # valid but CSS2: font with name serif, CSS3: same as `serif` '"serif"', 'a b', # should use quotes but valid 'a, b b, d', ), ): (True, True, CSS2), ( 'font-weight', ( 'normal', 'bold', 'bolder', 'lighter', 'inherit', '100', '200', '300', '400', '500', '600', '700', '800', '900', ), ): (True, True, CSS2), ( 'font-stretch', ( 'normal', 'wider', 'narrower', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded', 'inherit', ), ): (True, True, FM3), ('font-style', ('normal', 'italic', 'oblique', 'inherit')): ( True, True, CSS2, ), ('font-variant', ('normal', 'small-caps', 'inherit')): (True, True, CSS2), ('font-size', ('-1em',)): (False, False, CSS2), ( 'font-size', ( 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'larger', 'smaller', '1em', '1%', 'inherit', ), ): (True, True, CSS2), ('font-size-adjust', ('1.0', 'none', 'inherit')): (True, True, FM3), ( 'font', ( 'italic small-caps bold 1px/3 a, "b", serif', '12pt/14pt sans-serif', '80% sans-serif', 'x-large/110% "new century schoolbook", serif', 'bold italic large Palatino, serif', 'normal small-caps 120%/120% fantasy', 'oblique 12pt "Helvetica Nue", serif', 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', 'inherit', ), ): (True, True, CSS2), ('nav-index', ('1', 'auto', 'inherit')): (True, True, C3BUI), ('nav-index', ('x', '1 2', '1px')): (False, False, C3BUI), ( 'opacity', ( 'inherit', '0', '0.0', '0.42342', '1', '1.0', # should be clipped but valid '-0', '-0.1', '-10', '2', ), ): (True, True, CM3), ('opacity', ('a', '#000', '+1')): (False, False, CM3), ( 'outline', ( 'red dotted 1px', 'dotted 1px red', '1px red dotted', 'red', '1px', 'dotted', 'red 1px', '1px dotted', 'red dotted', 'inherit', ), ): (True, True, C3BUI), ('outline', ('red #fff', 'solid dotted', 'Url(x)', '1px 1px')): ( False, False, C3BUI, ), ('outline-color', ('red', '#fff', 'inherit')): (True, True, C3BUI), ('outline-color', ('0', '1em')): (False, False, C3BUI), ('outline-offset', ('0', '1em', 'inherit')): (True, True, C3BUI), ('outline-offset', ('1%', 'red')): (False, False, C3BUI), ('outline-style', ('auto', 'dotted', 'inherit')): (True, True, C3BUI), ('outline-style', ('0', '1em', 'red')): (False, False, C3BUI), ('outline-width', ('0', '1em', 'inherit')): (True, True, C3BUI), ('outline-width', ('auto', 'red', 'dotted')): (False, False, C3BUI), ('resize', ('none', 'both', 'horizontal', 'vertical', 'inherit')): ( True, True, C3BUI, ), ('resize', ('1', 'auto', '1px', '2%')): (False, False, C3BUI), ( 'size', ( '1cm', '1mm 20cm', 'auto', 'landscape letter', 'a4 portrait', 'landscape', 'a5', # 'inherit' ), ): (True, True, C3PM), ('size', ('portrait landscape', 'a5 letter', '2%')): (False, False, C3PM), ( 'src', ( 'url( a )', 'local( x )', 'local("x")', 'local( "x" )', 'url(../fonts/LateefRegAAT.ttf) format( "truetype-aat" )', 'url(a) format( "123x" , "a" )', 'url(a) format( "123x" , "a" ), ' 'url(a) format( "123x" , "a" )', 'local(HiraKakuPro-W3), local(Meiryo), local(IPAPGothic)', 'local(Gentium), url(/fonts/Gentium.ttf)', 'local("Gentium"), url("/fonts/Gentium.ttf")', 'local(Futura-Medium), url(fonts.svg#MyGeometricModern) ' 'format("svg")', ), ): (True, True, FM3FF), ( 'text-shadow', ( 'none', '1px 1px', '1px 1px 1px', '1px 1px 1px 1px', '1px 1px 1px 1px red', 'inset 1px 1px', 'inset 1px 1px 1px 1px black', ), ): (True, True, C3T), ( 'text-shadow', ( '1px', '1px 1px 1px 1px 1px', 'x 1px 1px', 'inset', '1px black', 'black', ), ): (False, False, C3T), ('unicode-range', ('u+1', 'U+111111-ffffff', 'u+123456 , U+1-f')): ( True, True, FM3FF, ), } # TODO!!! for (name, values), (valid, matching, profile) in list(tests.items()): for value in values: assert valid == cssutils.profile.validate(name, value) # assert ( # valid, # matching, # list(profile), # ) == cssutils.profile.validateWithProfile(name, value) def _gen_validation_inputs(): yield ( (FM3FF, 'font-family', ('y', '"y"')), ( # => name should be "y"!!! True, True, FM3FF, ), ) yield ((FM3FF, 'font-family', ('"y", "a"', 'a, b', 'a a')), (True, False, CSS2)) yield ( (FM3FF, 'font-stretch', ('normal', 'wider', 'narrower', 'inherit')), ( True, False, FM3, ), ) yield ((FM3FF, 'font-style', ('inherit',)), (True, False, CSS2)) yield ( ( FM3FF, 'font-weight', ( 'bolder', 'lighter', 'inherit', ), ), (True, False, CSS2), ) @pytest.mark.parametrize(('params', 'results'), _gen_validation_inputs()) @pytest.mark.xfail(reason="#37") def test_validateWithProfile_fonts(self, params, results): "Profiles.validateWithProfile()" # testing for valid values overwritten in a profile v, m, p = results expected = v, m, list(p) assert cssutils.profile.validateWithProfile(*params) == expected