diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index c7826aef2..891e56bf5 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -384,7 +384,7 @@ class Config(QObject): raise configexc.BackendError(objects.backend) opt.typ.to_py(value) # for validation - self._values[opt.name] = value + self._values[opt.name] = opt.typ.from_obj(value) self.changed.emit(opt.name) log.config.debug("Config option changed: {} = {}".format( diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index bc5db6d94..afe9eb372 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -227,6 +227,10 @@ class BaseType: return None return value + def from_obj(self, value): + """Get the setting value from a config.py/YAML object.""" + return value + def to_py(self, value): """Get the setting value from a Python value. @@ -441,6 +445,11 @@ class List(BaseType): self.to_py(yaml_val) return yaml_val + def from_obj(self, value): + if value is None: + return [] + return value + def to_py(self, value): self._basic_py_validation(value, list) if not value: @@ -506,6 +515,11 @@ class ListOrValue(BaseType): except configexc.ValidationError: return self.valtype.from_str(value) + def from_obj(self, value): + if value is None: + return [] + return value + def to_py(self, value): try: return [self.valtype.to_py(value)] @@ -1176,6 +1190,11 @@ class Dict(BaseType): self.to_py(yaml_val) return yaml_val + def from_obj(self, value): + if value is None: + return {} + return value + def _fill_fixed_keys(self, value): """Fill missing fixed keys with a None-value.""" if self.fixed_keys is None: diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 949064bc9..43af839ba 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -547,6 +547,14 @@ class TestBindConfigCommand: commands.bind(key, 'message-info foo', mode='normal') assert keyconf.get_command(key, 'normal') == 'nop' + def test_bind_none(self, commands, config_stub): + config_stub.val.bindings.commands = None + commands.bind(',x', 'nop') + + def test_unbind_none(self, commands, config_stub): + config_stub.val.bindings.commands = None + commands.unbind('H') + @pytest.mark.parametrize('key, normalized', [ ('a', 'a'), # default bindings ('b', 'b'), # custom bindings diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index d987d7442..6274b7d50 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -378,6 +378,13 @@ class TestConfigPy: expected = "Duplicate key H - use force=True to override!" assert str(error.exception) == expected + def test_bind_none(self, confpy): + confpy.write("c.bindings.commands = None", + "config.bind(',x', 'nop')") + confpy.read() + expected = {'normal': {',x': 'nop'}} + assert config.instance._values['bindings.commands'] == expected + @pytest.mark.parametrize('line, key, mode', [ ('config.unbind("o")', 'o', 'normal'), ('config.unbind("y", mode="prompt")', 'y', 'prompt'), diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 1c6ed7f08..9b5e23263 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -370,6 +370,10 @@ class TestBaseType: def test_to_doc(self, klass, value, expected): assert klass().to_doc(value) == expected + @pytest.mark.parametrize('obj', [42, '', None, 'foo']) + def test_from_obj(self, klass, obj): + assert klass(none_ok=True).from_obj(obj) == obj + class MappingSubclass(configtypes.MappingType): @@ -550,6 +554,14 @@ class TestList: with pytest.raises(configexc.ValidationError): klass().from_str(val) + @pytest.mark.parametrize('obj, expected', [ + ([1], [1]), + ([], []), + (None, []), + ]) + def test_from_obj(self, klass, obj, expected): + assert klass(none_ok_outer=True).from_obj(obj) == expected + @pytest.mark.parametrize('val', [['foo'], ['foo', 'bar']]) def test_to_py_valid(self, klass, val): assert klass().to_py(val) == val @@ -713,6 +725,15 @@ class TestListOrValue: def test_to_py_length(self, strtype, klass, val): klass(strtype, none_ok=True, length=2).to_py(val) + @pytest.mark.parametrize('obj, expected', [ + (['a'], ['a']), + ([], []), + (None, []), + ]) + def test_from_obj(self, klass, obj, expected): + typ = klass(none_ok=True, valtype=configtypes.String()) + assert typ.from_obj(obj) == expected + @pytest.mark.parametrize('val', [['a'], ['a', 'b'], ['a', 'b', 'c', 'd']]) def test_wrong_length(self, strtype, klass, val): with pytest.raises(configexc.ValidationError, @@ -1533,6 +1554,16 @@ class TestDict: valtype=configtypes.Int()) assert typ.from_str('{"answer": 42}') == {"answer": 42} + @pytest.mark.parametrize('obj, expected', [ + ({'a': 'b'}, {'a': 'b'}), + ({}, {}), + (None, {}), + ]) + def test_from_obj(self, klass, obj, expected): + d = klass(keytype=configtypes.String(), valtype=configtypes.String(), + none_ok=True) + assert d.from_obj(obj) == expected + @pytest.mark.parametrize('keytype, valtype, val', [ (configtypes.String(), configtypes.String(), {'hello': 'world'}), (configtypes.String(), configtypes.Int(), {'hello': 42}),