mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			234 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
 | 
						|
from typing import cast, Any
 | 
						|
 | 
						|
import sys
 | 
						|
import unittest
 | 
						|
 | 
						|
try:
 | 
						|
    from tools.lib.css_parser import (
 | 
						|
        CssParserException,
 | 
						|
        CssSection,
 | 
						|
        parse,
 | 
						|
        handle_prefluff,
 | 
						|
        handle_postfluff
 | 
						|
    )
 | 
						|
except ImportError:
 | 
						|
    print('ERROR!!! You need to run this via tools/test-tools.')
 | 
						|
    sys.exit(1)
 | 
						|
 | 
						|
class ParserTestHappyPath(unittest.TestCase):
 | 
						|
    def test_basic_parse(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_selector = 'li.foo'
 | 
						|
        my_block = '''{
 | 
						|
                color: red;
 | 
						|
            }'''
 | 
						|
        my_css = my_selector + ' ' + my_block
 | 
						|
        res = parse(my_css)
 | 
						|
        self.assertEqual(res.text(), 'li.foo {\n    color: red;\n}')
 | 
						|
        section = cast(CssSection, res.sections[0])
 | 
						|
        block = section.declaration_block
 | 
						|
        self.assertEqual(block.text().strip(), '{\n    color: red;\n}')
 | 
						|
        declaration = block.declarations[0]
 | 
						|
        self.assertEqual(declaration.css_property, 'color')
 | 
						|
        self.assertEqual(declaration.css_value.text().strip(), 'red')
 | 
						|
 | 
						|
    def test_same_line_comment(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            li.hide {
 | 
						|
                display: none; /* comment here */
 | 
						|
                /* Not to be confused
 | 
						|
                   with this comment */
 | 
						|
                color: green;
 | 
						|
            }'''
 | 
						|
        res = parse(my_css)
 | 
						|
        section = cast(CssSection, res.sections[0])
 | 
						|
        block = section.declaration_block
 | 
						|
        declaration = block.declarations[0]
 | 
						|
        self.assertIn('/* comment here */', declaration.text())
 | 
						|
 | 
						|
    def test_no_semicolon(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            p { color: red }
 | 
						|
        '''
 | 
						|
 | 
						|
        reformatted_css = '\np {\n    color: red;\n}\n'
 | 
						|
 | 
						|
        res = parse(my_css)
 | 
						|
 | 
						|
        self.assertEqual(res.text(), reformatted_css)
 | 
						|
 | 
						|
        section = cast(CssSection, res.sections[0])
 | 
						|
 | 
						|
        self.assertFalse(section.declaration_block.declarations[0].semicolon)
 | 
						|
 | 
						|
    def test_empty_block(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            div {
 | 
						|
            }'''
 | 
						|
        error = 'Empty declaration'
 | 
						|
        with self.assertRaisesRegex(CssParserException, error):
 | 
						|
            parse(my_css)
 | 
						|
 | 
						|
    def test_multi_line_selector(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            h1,
 | 
						|
            h2,
 | 
						|
            h3 {
 | 
						|
                top: 0
 | 
						|
            }'''
 | 
						|
        res = parse(my_css)
 | 
						|
        section = res.sections[0]
 | 
						|
        selectors = section.selector_list.selectors
 | 
						|
        self.assertEqual(len(selectors), 3)
 | 
						|
 | 
						|
    def test_comment_at_end(self):
 | 
						|
        # type: () -> None
 | 
						|
        '''
 | 
						|
        This test verifies the current behavior, which is to
 | 
						|
        attach comments to the preceding rule, but we should
 | 
						|
        probably change it so the comments gets attached to
 | 
						|
        the next block, if possible.
 | 
						|
        '''
 | 
						|
        my_css = '''
 | 
						|
            p {
 | 
						|
                color: black;
 | 
						|
            }
 | 
						|
 | 
						|
            /* comment at the end of the text */
 | 
						|
            '''
 | 
						|
        res = parse(my_css)
 | 
						|
        self.assertEqual(len(res.sections), 1)
 | 
						|
        section = res.sections[0]
 | 
						|
        self.assertIn('comment at the end', section.post_fluff)
 | 
						|
 | 
						|
    def test_media_block(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            @media (max-width: 300px) {
 | 
						|
                h5 {
 | 
						|
                    margin: 0;
 | 
						|
                }
 | 
						|
            }'''
 | 
						|
        res = parse(my_css)
 | 
						|
        self.assertEqual(len(res.sections), 1)
 | 
						|
        self.assertEqual(res.text(), '\n            @media (max-width: 300px) {\n    h5 {\n        margin: 0;\n    }\n}')
 | 
						|
 | 
						|
    def test_handle_prefluff(self):
 | 
						|
        # type: () -> None
 | 
						|
        PREFLUFF = '  \n  '
 | 
						|
        PREFLUFF1 = ' '
 | 
						|
        PREFLUFF2 = ' /* some comment \nhere */'
 | 
						|
        PREFLUFF3 = '\n /* some comment \nhere */'
 | 
						|
        self.assertEqual(handle_prefluff(PREFLUFF), '\n')
 | 
						|
        self.assertEqual(handle_prefluff(PREFLUFF, True), '\n    ')
 | 
						|
        self.assertEqual(handle_prefluff(PREFLUFF1), '')
 | 
						|
        self.assertEqual(handle_prefluff(PREFLUFF1, True), '\n    ')
 | 
						|
        self.assertEqual(handle_prefluff(PREFLUFF2), '/* some comment\n   here */\n')
 | 
						|
        self.assertEqual(handle_prefluff(PREFLUFF3, True), '\n    /* some comment\n       here */\n    ')
 | 
						|
 | 
						|
    def test_handle_postfluff(self):
 | 
						|
        # type: () -> None
 | 
						|
        POSTFLUFF = '/* Comment Here */'
 | 
						|
        POSTFLUFF1 = '/* Comment \nHere */'
 | 
						|
        POSTFLUFF2 = ' '
 | 
						|
        POSTFLUFF3 = '\n /* some comment \nhere */'
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF), '/* Comment Here */\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF, space_after_first_line=True), ' /* Comment Here */\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF, indent=True, space_after_first_line=True), ' /* Comment Here */\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF1), '/* Comment\n   Here */')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF1, space_after_first_line=True), ' /* Comment\n   Here */\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF1, indent=True, space_after_first_line=True), ' /* Comment\n       Here */\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF2), '')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF2, space_after_first_line=True), '')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF2, indent=True, space_after_first_line=True), '\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF3), '\n/* some comment\n   here */')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF3, space_after_first_line=True), '\n/* some comment\n   here */\n')
 | 
						|
        self.assertEqual(handle_postfluff(POSTFLUFF3, indent=True, space_after_first_line=True), '\n    /* some comment\n       here */\n')
 | 
						|
 | 
						|
class ParserTestSadPath(unittest.TestCase):
 | 
						|
    '''
 | 
						|
    Use this class for tests that verify the parser will
 | 
						|
    appropriately choke on malformed CSS.
 | 
						|
 | 
						|
    We prevent some things that are technically legal
 | 
						|
    in CSS, like having comments in the middle of list
 | 
						|
    of selectors.  Some of this is just for expediency;
 | 
						|
    some of this is to enforce consistent formatting.
 | 
						|
    '''
 | 
						|
    def _assert_error(self, my_css, error):
 | 
						|
        # type: (str, str) -> None
 | 
						|
        with self.assertRaisesRegex(CssParserException, error):
 | 
						|
            parse(my_css)
 | 
						|
 | 
						|
    def test_unexpected_end_brace(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            @media (max-width: 975px) {
 | 
						|
                body {
 | 
						|
                    color: red;
 | 
						|
                }
 | 
						|
            }} /* whoops */'''
 | 
						|
        error = 'unexpected }'
 | 
						|
        self._assert_error(my_css, error)
 | 
						|
 | 
						|
    def test_empty_section(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
 | 
						|
            /* nothing to see here, move along */
 | 
						|
            '''
 | 
						|
        error = 'unexpected empty section'
 | 
						|
        self._assert_error(my_css, error)
 | 
						|
 | 
						|
    def test_missing_colon(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            .hide
 | 
						|
            {
 | 
						|
                display none /* no colon here */
 | 
						|
            }'''
 | 
						|
        error = 'We expect a colon here'
 | 
						|
        self._assert_error(my_css, error)
 | 
						|
 | 
						|
    def test_unclosed_comment(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = ''' /* comment with no end'''
 | 
						|
        error = 'unclosed comment'
 | 
						|
        self._assert_error(my_css, error)
 | 
						|
 | 
						|
    def test_missing_selectors(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            /* no selectors here */
 | 
						|
            {
 | 
						|
                bottom: 0
 | 
						|
            }'''
 | 
						|
        error = 'Missing selector'
 | 
						|
        self._assert_error(my_css, error)
 | 
						|
 | 
						|
    def test_missing_value(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            h1
 | 
						|
            {
 | 
						|
                bottom:
 | 
						|
            }'''
 | 
						|
        error = 'Missing value'
 | 
						|
        self._assert_error(my_css, error)
 | 
						|
 | 
						|
    def test_disallow_comments_in_selectors(self):
 | 
						|
        # type: () -> None
 | 
						|
        my_css = '''
 | 
						|
            h1,
 | 
						|
            h2, /* comment here not allowed by Zulip */
 | 
						|
            h3 {
 | 
						|
                top: 0
 | 
						|
            }'''
 | 
						|
        error = 'Comments in selector section are not allowed'
 | 
						|
        self._assert_error(my_css, error)
 |