openai/chatkit-python

Public

mirrored fromhttps://github.com/openai/chatkit-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4e1156dd81f931ed2f455197737892dd59437ab2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tests/test_widgets.py

261lines · modecode

1import json
2from datetime import datetime
3from typing import Any, Literal
4
5import pytest
6
7from chatkit.server import diff_widget
8from chatkit.types import WidgetItem
9from chatkit.widgets import (
10 BasicRoot,
11 Card,
12 DynamicWidgetComponent,
13 DynamicWidgetRoot,
14 Text,
15 WidgetRoot,
16 WidgetTemplate,
17)
18
19
20@pytest.mark.parametrize(
21 "before, after, expected",
22 [
23 (Card(children=[]), Card(children=[]), []),
24 (
25 Card(children=[Text(id="text", value="Hello", streaming=True)]),
26 Card(children=[Text(id="text", value="Hello, world!", streaming=True)]),
27 ["widget.streaming_text.value_delta"],
28 ),
29 (
30 Card(children=[Text(id="text", value="Hello", streaming=True)]),
31 Card(children=[Text(id="text", value="Hello, world!", streaming=False)]),
32 ["widget.root.updated"],
33 ),
34 (
35 Card(children=[Text(value="Hello")]),
36 Card(children=[Text(value="world!")]),
37 ["widget.root.updated"],
38 ),
39 # DynamicWidgetRoot tests
40 (
41 DynamicWidgetRoot(type="Card", children=[]),
42 DynamicWidgetRoot(type="Card", children=[]),
43 [],
44 ),
45 (
46 DynamicWidgetRoot(
47 type="Card",
48 children=[
49 DynamicWidgetComponent.model_validate({
50 "type": "Text",
51 "id": "text",
52 "value": "Hello",
53 "streaming": True,
54 })
55 ],
56 ),
57 DynamicWidgetRoot(
58 type="Card",
59 children=[
60 DynamicWidgetComponent.model_validate({
61 "type": "Text",
62 "id": "text",
63 "value": "Hello, world!",
64 "streaming": True,
65 })
66 ],
67 ),
68 ["widget.streaming_text.value_delta"],
69 ),
70 (
71 DynamicWidgetRoot(
72 type="Card",
73 children=[
74 DynamicWidgetComponent.model_validate({
75 "type": "Text",
76 "id": "text",
77 "value": "Hello",
78 "streaming": True,
79 })
80 ],
81 ),
82 DynamicWidgetRoot(
83 type="Card",
84 children=[
85 DynamicWidgetComponent.model_validate({
86 "type": "Text",
87 "id": "text",
88 "value": "Hello, world!",
89 "streaming": False,
90 })
91 ],
92 ),
93 ["widget.root.updated"],
94 ),
95 (
96 DynamicWidgetRoot(
97 type="Card",
98 children=[
99 DynamicWidgetComponent.model_validate({
100 "type": "Text",
101 "value": "Hello",
102 })
103 ],
104 ),
105 DynamicWidgetRoot(
106 type="Card",
107 children=[
108 DynamicWidgetComponent.model_validate({
109 "type": "Text",
110 "value": "world!",
111 })
112 ],
113 ),
114 ["widget.root.updated"],
115 ),
116 ],
117)
118def test_diff(
119 before: WidgetRoot,
120 after: WidgetRoot,
121 expected: list[
122 Literal[
123 "widget.streaming_text.value_delta",
124 "widget.root.updated",
125 ]
126 ],
127):
128 diff = diff_widget(before, after)
129 assert len(diff) == len(expected)
130 for i in range(len(diff)):
131 assert diff[i].type == expected[i]
132
133
134def test_json_dump_excludes_none_fields():
135 widget = Card(children=[Text(value="Hello")])
136
137 json_str = widget.model_dump_json()
138 assert isinstance(json_str, str)
139 data = json.loads(json_str)
140
141 # Top-level widget should include type and exclude None-valued fields.
142 assert data["type"] == "Card"
143 assert "key" not in data
144 assert "padding" not in data
145 assert "status" not in data
146 assert "collapsed" not in data
147
148 # Children should be serialized with None fields omitted as well.
149 assert isinstance(data["children"], list)
150 assert len(data["children"]) == 1
151
152 text_dump = data["children"][0]
153 assert text_dump["type"] == "Text"
154 assert text_dump["value"] == "Hello"
155 assert "italic" not in text_dump
156 assert "streaming" not in text_dump
157 assert "color" not in text_dump
158 assert "key" not in text_dump
159
160
161def test_json_dump_excludes_none_fields_nested():
162 widget = Card(children=[Text(value="Hello")])
163 widget_item = WidgetItem(
164 thread_id="1", widget=widget, id="1", created_at=datetime.now()
165 )
166
167 json_str = widget_item.model_dump_json()
168 assert isinstance(json_str, str)
169 data = json.loads(json_str)
170
171 # Top-level widget should include type and exclude None-valued fields.
172 widget_dump = data["widget"]
173 assert widget_dump["type"] == "Card"
174 assert "key" not in widget_dump
175 assert "padding" not in widget_dump
176 assert "status" not in widget_dump
177 assert "collapsed" not in widget_dump
178
179 # Children should be serialized with None fields omitted as well.
180 assert isinstance(widget_dump["children"], list)
181 assert len(widget_dump["children"]) == 1
182
183 text_dump = widget_dump["children"][0]
184 assert text_dump["type"] == "Text"
185 assert text_dump["value"] == "Hello"
186 assert "italic" not in text_dump
187 assert "streaming" not in text_dump
188 assert "color" not in text_dump
189 assert "key" not in text_dump
190
191
192@pytest.mark.parametrize(
193 "widget_name, data",
194 [
195 ("list_view_no_data", None),
196 ("card_no_data", None),
197 (
198 "list_view_with_data",
199 {
200 "items": [
201 {
202 "id": "blue",
203 "label": "Blue line",
204 "color": "blue-500",
205 },
206 {
207 "id": "orange",
208 "label": "Orange line",
209 "color": "orange-500",
210 },
211 {
212 "id": "purple",
213 "label": "Purple line",
214 "color": "purple-500",
215 },
216 ],
217 },
218 ),
219 (
220 "card_with_data",
221 {
222 "channel": "#proj-chatkit",
223 "time": "4:48 PM",
224 "user": {
225 "image": "/pam.png",
226 "name": "Pam Beesly",
227 },
228 },
229 ),
230 ],
231)
232def test_widget_template_from_file(
233 widget_name: str,
234 data: dict[str, Any] | None,
235):
236 template = WidgetTemplate.from_file(f"assets/widgets/{widget_name}.widget")
237
238 with open(f"tests/assets/widgets/{widget_name}.json", "r") as file:
239 expected_widget_dict = json.load(file)
240
241 widget = template.build(data)
242
243 assert isinstance(widget, DynamicWidgetRoot)
244 assert widget.model_dump(exclude_none=True) == expected_widget_dict
245
246
247def test_widget_template_with_basic_root():
248 template = WidgetTemplate.from_file("assets/widgets/basic_root.widget")
249
250 with open("tests/assets/widgets/basic_root.json", "r") as file:
251 expected_widget_dict = json.load(file)
252
253 widget = template.build_basic(
254 {
255 "name": "Harry Potter",
256 "bio": "The boy who lived",
257 },
258 )
259
260 assert isinstance(widget, BasicRoot)
261 assert widget.model_dump(exclude_none=True) == expected_widget_dict
262