diff --git a/chatkit/server.py b/chatkit/server.py index 865135f..8c5c1c7 100644 --- a/chatkit/server.py +++ b/chatkit/server.py @@ -143,7 +143,9 @@ def full_replace_value(before_value: Any, after_value: Any) -> bool: ): # Appends to the value prop of Markdown or Text do not trigger a full replace continue - if full_replace_value(getattr(before, field), getattr(after, field)): + if full_replace_value( + getattr(before, field, None), getattr(after, field, None) + ): return True return False diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 18c56a2..fc9539a 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -5,7 +5,7 @@ import pytest from chatkit.server import diff_widget -from chatkit.types import WidgetItem +from chatkit.types import WidgetItem, WidgetRootUpdated from chatkit.widgets import ( BasicRoot, Card, @@ -131,6 +131,36 @@ def test_diff( assert diff[i].type == expected[i] +@pytest.mark.parametrize("reverse", [False, True]) +@pytest.mark.parametrize( + "before, after", + [ + ( + {"type": "Card", "children": []}, + {"type": "Card", "children": [], "size": "sm"}, + ), + ( + {"type": "Card", "children": [{"type": "Text", "value": "Hello"}]}, + { + "type": "Card", + "children": [{"type": "Text", "value": "Hello", "color": "secondary"}], + }, + ), + ], +) +def test_diff_replaces_dynamic_widgets_when_properties_are_added_or_removed( + before: dict[str, Any], after: dict[str, Any], reverse: bool +): + before_widget = DynamicWidgetRoot.model_validate(before) + after_widget = DynamicWidgetRoot.model_validate(after) + if reverse: + before_widget, after_widget = after_widget, before_widget + + assert diff_widget(before_widget, after_widget) == [ + WidgetRootUpdated(widget=after_widget) + ] + + def test_json_dump_excludes_none_fields(): widget = Card(children=[Text(value="Hello")])