mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
This is a fairly major overhaul of the CSS parser to support line numbers in error messages. Basically, instead of passing "slices" of tokens around, we pass indexes into the token arrays to all of our sub-parsers, which allows them to have access to previous tokens in certain cases. This is particularly important for errors where stuff is missing (vs. being wrong). In testing this out I found a few more places to catch errors.
216 lines
6.4 KiB
Python
216 lines
6.4 KiB
Python
from __future__ import absolute_import
|
|
from __future__ import print_function
|
|
|
|
from typing import cast, Any
|
|
|
|
import sys
|
|
import unittest
|
|
|
|
try:
|
|
from tools.lib.css_parser import (
|
|
CssParserException,
|
|
CssSection,
|
|
parse,
|
|
)
|
|
except ImportError:
|
|
print('ERROR!!! You need to run this via tools/test-tools.')
|
|
sys.exit(1)
|
|
|
|
class ParserTestHappyPath(unittest.TestCase):
|
|
def __init__(self, *args, **kwargs):
|
|
# type: (*Any, **Any) -> None
|
|
# This method should be removed when we migrate to version 3 of Python
|
|
import six
|
|
if six.PY2:
|
|
self.assertRaisesRegex = self.assertRaisesRegexp # type: ignore
|
|
super(ParserTestHappyPath, self).__init__(*args, **kwargs)
|
|
|
|
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(), my_css)
|
|
section = cast(CssSection, res.sections[0])
|
|
block = section.declaration_block
|
|
self.assertEqual(block.text().strip(), my_block)
|
|
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 }
|
|
'''
|
|
|
|
res = parse(my_css)
|
|
|
|
self.assertEqual(res.text(), my_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): # type: ignore # See https://github.com/python/typeshed/issues/372
|
|
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(), my_css)
|
|
|
|
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 __init__(self, *args, **kwargs):
|
|
# type: (*Any, **Any) -> None
|
|
# This method should be removed when we migrate to version 3 of Python
|
|
import six
|
|
if six.PY2:
|
|
self.assertRaisesRegex = self.assertRaisesRegexp # type: ignore
|
|
super(ParserTestSadPath, self).__init__(*args, **kwargs)
|
|
|
|
def _assert_error(self, my_css, error):
|
|
# type: (str, str) -> None
|
|
with self.assertRaisesRegex(CssParserException, error): # type: ignore # See https://github.com/python/typeshed/issues/372
|
|
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)
|