diff --git a/app/main/forms.py b/app/main/forms.py index ff4b9c6fe..6502c61e3 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -637,30 +637,6 @@ class RegisterUserFromOrgInviteForm(StripWhitespaceForm): auth_type = HiddenField('auth_type', validators=[DataRequired()]) -def extend_params(params, extensions): - items = None - param_items = len(params['items']) if 'items' in params else 0 - - # split items off from params to make it a pure dict - if 'items' in extensions: - items = extensions['items'] - del extensions['items'] - - # merge dicts - merge_jsonlike(params, extensions) - - # merge items - if items: - if 'items' not in params: - params['items'] = items - else: - for idx, _item in enumerate(items): - if idx >= param_items: - params['items'].append(items[idx]) - else: - params['items'][idx].update(items[idx]) - - def govuk_checkbox_field_widget(self, field, param_extensions=None, **kwargs): # error messages @@ -692,11 +668,11 @@ def govuk_checkbox_field_widget(self, field, param_extensions=None, **kwargs): # extend default params with any sent in during instantiation if self.param_extensions: - extend_params(params, self.param_extensions) + merge_jsonlike(params, self.param_extensions) # add any sent in though use in templates if param_extensions: - extend_params(params, param_extensions) + merge_jsonlike(params, param_extensions) return Markup( render_template('forms/fields/checkboxes/macro.njk', params=params)) @@ -748,11 +724,11 @@ def govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=False, param_ # extend default params with any sent in during instantiation if self.param_extensions: - extend_params(params, self.param_extensions) + merge_jsonlike(params, self.param_extensions) # add any sent in though use in templates if param_extensions: - extend_params(params, param_extensions) + merge_jsonlike(params, param_extensions) if wrap_in_collapsible: @@ -802,11 +778,11 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): # extend default params with any sent in during instantiation if self.param_extensions: - extend_params(params, self.param_extensions) + merge_jsonlike(params, self.param_extensions) # add any sent in though use in templates if param_extensions: - extend_params(params, param_extensions) + merge_jsonlike(params, param_extensions) return Markup( render_template('components/radios/template.njk', params=params)) diff --git a/app/utils.py b/app/utils.py index 41cdc29d1..554ccc83c 100644 --- a/app/utils.py +++ b/app/utils.py @@ -651,8 +651,13 @@ def merge_jsonlike(source, destination): return True def merge_lists(source, destination): - for item in destination: - if item not in source: + last_src_idx = len(source) - 1 + for idx, item in enumerate(destination): + if idx <= last_src_idx: + # assign destination value if can't be merged into source + if merge_items(source[idx], destination[idx]) is False: + source[idx] = destination[idx] + else: source.append(item) def merge_dicts(source, destination): diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index 4246b5037..1f5cd0c8e 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -614,18 +614,30 @@ def test_get_sample_template_returns_template(template_type): ({"a": "b"}, {"c": "d"}, {"a": "b", "c": "d"}), # dicts with nested dict, both under same key, additive behaviour: ({"a": {"b": "c"}}, {"a": {"e": "f"}}, {"a": {"b": "c", "e": "f"}}), - # same key in both dicts, value is a string, destination supersedes source: + # same key in both dicts, value is a string, destination supercedes source: ({"a": "b"}, {"a": "c"}, {"a": "c"}), + # nested dict added to new key of dict, additive behaviour: + ({"a": "b"}, {"c": {"d": "e"}}, {"a": "b", "c": {"d": "e"}}), + # lists with same length but different items, destination supercedes source: + (["b", "c", "d"], ["b", "e", "f"], ["b", "e", "f"]), # lists in dicts behave as top level lists - ({"a": ["b", "c", "d"]}, {"a": ["b", "e", "f"]}, {"a": ["b", "c", "d", "e", "f"]}), - # lists with same string in both result in a list of unique values - (["a", "b", "c", "d"], ["d", "e", "f"], ["a", "b", "c", "d", "e", "f"]), + ({"a": ["b", "c", "d"]}, {"a": ["b", "e", "f"]}, {"a": ["b", "e", "f"]}), + # lists with same string in both, at different positions, result in duplicates keeping their positions + (["a", "b", "c", "d"], ["d", "e", "f"], ["d", "e", "f", "d"]), # lists with same dict in both result in a list with one instance of that dict ([{"b": "c"}], [{"b": "c"}], [{"b": "c"}]), # if dicts in lists have different values, they are not merged - ([{"b": "c"}], [{"b": "e"}], [{"b": "c"}, {"b": "e"}]), + ([{"b": "c"}], [{"b": "e"}], [{"b": "e"}]), + # if nested dicts in lists have different keys, additive behaviour + ([{"b": "c"}], [{"d": {"e": "f"}}], [{"b": "c", "d": {"e": "f"}}]), + # if dicts in destination list but not source, they just get added to end of source + ([{"a": "b"}], [{"a": "b"}, {"a": "b"}, {"c": "d"}], [{"a": "b"}, {"a": "b"}, {"c": "d"}]), # merge a dict with a null object returns that dict (does not work the other way round) ({"a": {"b": "c"}}, None, {"a": {"b": "c"}}), + # double nested dicts, new adds new Boolean key: value, additive behaviour + ({"a": {"b": {"c": "d"}}}, {"a": {"b": {"e": True}}}, {"a": {"b": {"c": "d", "e": True}}}), + # double nested dicts, both have same key, different values, destination supercedes source + ({"a": {"b": {"c": "d"}}}, {"a": {"b": {"c": "e"}}}, {"a": {"b": {"c": "e"}}}) ]) def test_merge_jsonlike_merges_jsonlike_objects_correctly(source_object, destination_object, expected_result): merge_jsonlike(source_object, destination_object)