Add support for more values in QtColor config type.

Recent changes in the completion highlighter mandate that
config.val.colors.completion.match be changed from a QssColor to a
QtColor. However, the latter accepts fewer formats. To avoid breaking
configs, this allows QtColors to be specified using all the same formats
as QssColors, excluding gradients.

I separated the QssColor and QtColor tests as the previous approach of
generating the tests made adding tests for QtColor more complicated.

While working on this I discovered that Qt's css parser is potentially
broken around parsing hsv percentages and filed
https://bugreports.qt.io/browse/QTBUG-70897.

For consistency, I made our parser similarly broken.

You can show the bug in qutebrowser right now by noting that the
following have different effects:

```
set colors.completion.odd.bg 'hsv(100%, 100%, 100%)'
set colors.completion.odd.bg 'hsv(358, 255, 255)'
```
This commit is contained in:
Ryan Roden-Corrent 2018-10-02 17:30:43 -04:00
parent 0fed563a02
commit 66cc5f5ea4
No known key found for this signature in database
GPG Key ID: 4E5072F68872BC04
2 changed files with 106 additions and 74 deletions

View File

@ -905,11 +905,42 @@ class QtColor(BaseType):
* transparent (no color)
"""
def _parse_value(self, val):
try:
return int(val)
except ValueError:
pass
mult = 255.0
if val.endswith('%'):
val = val[:-1]
mult = 255.0 / 100
return int(float(val) * 255.0 / 100.0)
try:
return int(float(val) * mult)
except ValueError:
raise configexc.ValidationError(val, "must be a valid color value")
def to_py(self, value):
self._basic_py_validation(value, str)
if not value:
return None
if value.endswith(')'):
openparen = value.index('(')
kind = value[:openparen]
vals = value[openparen+1:-1].split(',')
vals = [self._parse_value(v) for v in vals]
if kind == 'rgba' and len(vals) == 4:
return QColor.fromRgb(*vals)
if kind == 'rgb' and len(vals) == 3:
return QColor.fromRgb(*vals)
if kind == 'hsva' and len(vals) == 4:
return QColor.fromHsv(*vals)
if kind == 'hsv' and len(vals) == 3:
return QColor.fromHsv(*vals)
color = QColor(value)
if color.isValid():
return color

View File

@ -1222,87 +1222,88 @@ class TestCommand:
assert ('cmd2', "desc 2") in items
class ColorTests:
class TestQtColor:
"""Generator for tests for TestColors."""
"""Test QtColor."""
TYPES = [configtypes.QtColor, configtypes.QssColor]
@pytest.mark.parametrize('val, expected', [
('#123', QColor('#123')),
('#112233', QColor('#112233')),
('#111222333', QColor('#111222333')),
('#111122223333', QColor('#111122223333')),
('red', QColor('red')),
TESTS = [
('#123', TYPES),
('#112233', TYPES),
('#111222333', TYPES),
('#111122223333', TYPES),
('red', TYPES),
('rgb(0, 0, 0)', QColor.fromRgb(0, 0, 0)),
('rgb(0,0,0)', QColor.fromRgb(0, 0, 0)),
('#00000G', []),
('#123456789ABCD', []),
('#12', []),
('foobar', []),
('42', []),
('foo(1, 2, 3)', []),
('rgb(1, 2, 3', []),
('rgba(255, 255, 255, 1.0)', QColor.fromRgb(255, 255, 255, 255)),
('rgb(0, 0, 0)', [configtypes.QssColor]),
('rgb(0,0,0)', [configtypes.QssColor]),
# this should be (36, 25, 25) as hue goes to 359
# however this is consistent with Qt's CSS parser
# https://bugreports.qt.io/browse/QTBUG-70897
('hsv(10%,10%,10%)', QColor.fromHsv(25, 25, 25)),
])
def test_valid(self, val, expected):
act = configtypes.QtColor().to_py(val)
print(expected.hue(), expected.saturation(), expected.value(), expected.alpha())
print(act.hue(), act.saturation(), act.value(), act.alpha())
assert configtypes.QtColor().to_py(val) == expected
('rgba(255, 255, 255, 1.0)', [configtypes.QssColor]),
('hsv(10%,10%,10%)', [configtypes.QssColor]),
('qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, '
'stop: 0.4 gray, stop:1 green)', [configtypes.QssColor]),
('qconicalgradient(cx:0.5, cy:0.5, angle:30, stop:0 white, '
'stop:1 #00FF00)', [configtypes.QssColor]),
('qradialgradient(cx:0, cy:0, radius: 1, fx:0.5, fy:0.5, '
'stop:0 white, stop:1 green)', [configtypes.QssColor]),
]
COMBINATIONS = list(itertools.product(TESTS, TYPES))
def __init__(self):
self.valid = list(self._generate_valid())
self.invalid = list(self._generate_invalid())
def _generate_valid(self):
for (val, valid_classes), klass in self.COMBINATIONS:
if klass in valid_classes:
yield klass, val
def _generate_invalid(self):
for (val, valid_classes), klass in self.COMBINATIONS:
if klass not in valid_classes:
yield klass, val
class TestColors:
"""Test QtColor/QssColor."""
TESTS = ColorTests()
@pytest.fixture(params=ColorTests.TYPES)
def klass_fixt(self, request):
"""Fixture which provides all ColorTests classes.
Named klass_fix so it has a different name from the parametrized klass,
see https://github.com/pytest-dev/pytest/issues/979.
"""
return request.param
def test_test_generator(self):
"""Some sanity checks for ColorTests."""
assert self.TESTS.valid
assert self.TESTS.invalid
@pytest.mark.parametrize('klass, val', TESTS.valid)
def test_to_py_valid(self, klass, val):
expected = QColor(val) if klass is configtypes.QtColor else val
assert klass().to_py(val) == expected
@pytest.mark.parametrize('klass, val', TESTS.invalid)
def test_to_py_invalid(self, klass, val):
@pytest.mark.parametrize('val', [
'#00000G',
'#123456789ABCD',
'#12',
'foobar',
'42',
'foo(1, 2, 3)',
'rgb(1, 2, 3',
])
def test_invalid(self, val):
with pytest.raises(configexc.ValidationError):
klass().to_py(val)
configtypes.QtColor().to_py(val)
class TestQssColor:
"""Test QssColor."""
@pytest.mark.parametrize('val', [
'#123',
'#112233',
'#111222333',
'#111122223333',
'red',
'rgb(0, 0, 0)',
'rgb(0,0,0)',
'rgba(255, 255, 255, 1.0)',
'hsv(10%,10%,10%)',
'qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, '
'stop: 0.4 gray, stop:1 green)',
'qconicalgradient(cx:0.5, cy:0.5, angle:30, stop:0 white, '
'stop:1 #00FF00)',
'qradialgradient(cx:0, cy:0, radius: 1, fx:0.5, fy:0.5, '
'stop:0 white, stop:1 green)',
])
def test_valid(self, val):
assert configtypes.QssColor().to_py(val) == val
@pytest.mark.parametrize('val', [
'#00000G',
'#123456789ABCD',
'#12',
'foobar',
'42',
'foo(1, 2, 3)',
'rgb(1, 2, 3',
])
def test_invalid(self, val):
with pytest.raises(configexc.ValidationError):
configtypes.QssColor().to_py(val)
@attr.s