Files
presenton--presenton/servers/fastapi/tests/unit/test_presentation_template_v2_ui.py
2026-07-13 13:08:41 +08:00

912 lines
33 KiB
Python

from api.v1.ppt.endpoints import presentation as presentation_endpoint
from services.chat.memory_layer import PresentationChatMemoryLayer
def test_apply_template_v2_content_to_ui_uses_schema_content_keys():
ui = {
"id": "layout-1",
"description": "Layout with generated content placeholders.",
"components": [
{
"id": "hero",
"description": "Hero content component.",
"elements": [
{
"type": "text",
"decorative": False,
"name": "headline",
"runs": [{"text": "Old headline", "font": {"bold": True}}],
},
{
"type": "image",
"decorative": False,
"name": "hero_image",
"data": "/old-image.png",
"is_icon": False,
},
{
"type": "text-list",
"decorative": False,
"name": "bullets",
"items": [[{"text": "Old bullet"}]],
},
{
"type": "table",
"decorative": False,
"name": "metrics",
"columns": [{"runs": [{"text": "Old"}]}],
"rows": [[{"runs": [{"text": "Old value"}]}]],
},
{
"type": "chart",
"decorative": False,
"name": "trend",
"chart_type": "bar",
"categories": ["Old"],
"series": [{"name": "Old", "values": [1]}],
},
{
"type": "grid",
"name": "cards",
"children": [
{
"type": "group",
"name": "card_1",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_1",
"runs": [{"text": "Old card 1"}],
},
{
"type": "image",
"decorative": False,
"name": "icon_1",
"data": "/old-icon-1.svg",
"is_icon": True,
},
],
},
{
"type": "group",
"name": "card_2",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_2",
"runs": [{"text": "Old card 2"}],
},
{
"type": "image",
"decorative": False,
"name": "icon_2",
"data": "/old-icon-2.svg",
"is_icon": True,
},
],
},
],
},
],
},
{
"id": "metric_card",
"description": "First metric card.",
"elements": [
{
"type": "text",
"decorative": False,
"name": "value",
"runs": [{"text": "0%"}],
}
],
},
{
"id": "metric_card",
"description": "Second metric card.",
"elements": [
{
"type": "text",
"decorative": False,
"name": "value",
"runs": [{"text": "0%"}],
}
],
},
],
}
content = {
"hero": {
"headline": "Generated headline",
"hero_image": {
"image_prompt": "Team dashboard",
"image_url": "/app_data/images/hero.png",
},
"bullets": ["First generated bullet", "Second generated bullet"],
"metrics": {
"columns": ["Region", "Sales"],
"rows": [["US", 12], ["EU", True]],
},
"trend": {
"title": "Growth",
"chart_type": "line",
"categories": ["Q1", "Q2"],
"series": [{"name": "Revenue", "values": [10, 20]}],
"data_labels": "top",
},
"cards": [
{
"title": "First card",
"icon": {"icon_query": "growth", "icon_url": "/icons/one.svg"},
},
{
"title": "Second card",
"icon": {"icon_query": "support", "icon_url": "/icons/two.svg"},
},
],
},
"metric_card_0": {"value": "42%"},
"metric_card_1": {"value": "21%"},
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
hero_elements = hydrated["components"][0]["elements"]
assert hero_elements[0]["runs"] == [
{"text": "Generated headline", "font": {"bold": True}}
]
assert "text" not in hero_elements[0]
assert hero_elements[1]["data"] == "/app_data/images/hero.png"
assert hero_elements[1]["prompt"] == "Team dashboard"
assert hero_elements[2]["items"] == [
[{"text": "First generated bullet"}],
[{"text": "Second generated bullet"}],
]
assert hero_elements[3]["columns"][0]["runs"][0]["text"] == "Region"
assert hero_elements[3]["rows"][0][1]["runs"][0]["text"] == "12"
assert hero_elements[3]["rows"][1][1]["runs"][0]["text"] == "true"
assert hero_elements[4]["chart_type"] == "line"
assert hero_elements[4]["categories"] == ["Q1", "Q2"]
assert hero_elements[4]["data_labels"] == "top"
cards = hero_elements[5]["children"]
assert cards[0]["children"][0]["runs"][0]["text"] == "First card"
assert cards[0]["children"][1]["data"] == "/icons/one.svg"
assert cards[0]["children"][1]["prompt"] == "growth"
assert cards[1]["children"][0]["runs"][0]["text"] == "Second card"
assert cards[1]["children"][1]["data"] == "/icons/two.svg"
assert cards[1]["children"][1]["prompt"] == "support"
assert hydrated["components"][1]["elements"][0]["runs"][0]["text"] == "42%"
assert hydrated["components"][2]["elements"][0]["runs"][0]["text"] == "21%"
assert "text" not in hydrated["components"][1]["elements"][0]
assert "text" not in hydrated["components"][2]["elements"][0]
assert ui["components"][0]["elements"][0]["runs"][0]["text"] == "Old headline"
def test_apply_template_v2_content_to_ui_handles_empty_text_runs():
ui = {
"id": "layout-1",
"components": [
{
"id": "hero",
"elements": [
{
"type": "text",
"decorative": False,
"name": "headline",
"font": {"size": 24},
"runs": [],
},
{
"type": "table",
"decorative": False,
"name": "metrics",
"columns": [{"font": {"size": 10}, "runs": []}],
"rows": [[{"font": {"size": 9}, "runs": []}]],
},
],
}
],
}
content = {
"hero": {
"headline": "Generated headline",
"metrics": {
"columns": ["Metric"],
"rows": [["Revenue"]],
},
}
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
elements = hydrated["components"][0]["elements"]
assert elements[0]["runs"] == [{"text": "Generated headline", "font": {"size": 24}}]
assert elements[1]["columns"][0]["runs"] == [
{"text": "Metric", "font": {"size": 10}}
]
assert elements[1]["rows"][0][0]["runs"] == [
{"text": "Revenue", "font": {"size": 9}}
]
def test_chat_template_v2_content_hydration_updates_nested_blocks_and_tables():
ui = {
"id": "layout-1",
"components": [
{
"id": "hero",
"elements": [
{
"type": "text",
"decorative": False,
"name": "headline",
"runs": [{"text": "Old headline"}],
},
{
"type": "grid",
"name": "cards",
"children": [
{
"type": "group",
"name": "card_1",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_1",
"runs": [{"text": "Old card"}],
},
{
"type": "image",
"decorative": False,
"name": "icon_1",
"data": "/old.svg",
"is_icon": True,
},
],
}
],
},
{
"type": "table",
"decorative": False,
"name": "metrics",
"columns": [{"runs": [{"text": "Old column"}]}],
"rows": [[{"runs": [{"text": "Old value"}]}]],
},
],
}
],
}
PresentationChatMemoryLayer._apply_template_v2_content_to_ui(
ui,
{
"hero": {
"headline": "Generated headline",
"cards": [
{
"title": "Revenue up",
"icon": {
"icon_query": "growth",
"icon_url": "/app_data/icons/growth.svg",
},
}
],
"metrics": {
"columns": ["Metric", "Value"],
"rows": [["Revenue", 42]],
},
}
},
)
elements = ui["components"][0]["elements"]
assert elements[0]["runs"][0]["text"] == "Generated headline"
assert elements[1]["children"][0]["children"][0]["runs"][0]["text"] == "Revenue up"
assert elements[1]["children"][0]["children"][1]["data"] == "/app_data/icons/growth.svg"
assert elements[1]["children"][0]["children"][1]["prompt"] == "growth"
assert elements[2]["columns"][0]["runs"][0]["text"] == "Metric"
assert elements[2]["columns"][1]["runs"][0]["text"] == "Value"
assert elements[2]["rows"][0][0]["runs"][0]["text"] == "Revenue"
assert elements[2]["rows"][0][1]["runs"][0]["text"] == "42"
def test_apply_template_v2_content_to_ui_parses_markdown_text_to_runs():
ui = {
"id": "layout-1",
"description": "Layout with markdown generated content placeholders.",
"components": [
{
"id": "hero",
"description": "Hero content component.",
"elements": [
{
"type": "text",
"decorative": False,
"name": "headline",
"runs": [
{
"text": "Old headline",
"font": {"family": "Inter", "size": 24},
}
],
},
{
"type": "text-list",
"decorative": False,
"name": "bullets",
"items": [
[
{
"text": "Old bullet",
"font": {"color": "#111111", "size": 14},
}
]
],
},
{
"type": "table",
"decorative": False,
"name": "metrics",
"columns": [
{
"font": {"size": 10},
"runs": [
{
"text": "Old column",
"font": {"size": 10},
}
],
}
],
"rows": [
[
{
"runs": [
{
"text": "Old value",
"font": {"size": 9},
}
]
}
]
],
},
],
}
],
}
content = {
"hero": {
"headline": "AI **agents** need _memory_",
"bullets": [
"Keep **scope** tight",
"Ship *incrementally*",
],
"metrics": {
"columns": ["__Metric__"],
"rows": [["*Retention*"]],
},
}
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
elements = hydrated["components"][0]["elements"]
assert elements[0]["runs"] == [
{"text": "AI ", "font": {"family": "Inter", "size": 24}},
{
"text": "agents",
"font": {"family": "Inter", "size": 24, "bold": True},
},
{"text": " need ", "font": {"family": "Inter", "size": 24}},
{
"text": "memory",
"font": {"family": "Inter", "size": 24, "italic": True},
},
]
assert elements[1]["items"] == [
[
{"text": "Keep ", "font": {"color": "#111111", "size": 14}},
{
"text": "scope",
"font": {"color": "#111111", "size": 14, "bold": True},
},
{"text": " tight", "font": {"color": "#111111", "size": 14}},
],
[
{"text": "Ship "},
{
"text": "incrementally",
"font": {"italic": True},
},
],
]
assert elements[2]["columns"][0]["runs"] == [
{"text": "Metric", "font": {"size": 10, "bold": True}}
]
assert elements[2]["rows"][0][0]["runs"] == [
{"text": "Retention", "font": {"size": 9, "italic": True}}
]
def test_apply_template_v2_content_to_ui_markdown_overrides_inherited_emphasis():
ui = {
"id": "layout-1",
"components": [
{
"id": "information_card_grid",
"elements": [
{
"type": "group",
"name": "information_card",
"children": [
{
"type": "text",
"decorative": False,
"name": "card_body",
"font": {
"size": 21.33,
"family": "HK Grotesk",
"color": "#1E1E1E",
"wrap": "word",
},
"runs": [
{
"text": "Old body",
"font": {
"size": 21.33,
"family": "HK Grotesk",
"color": "#1E1E1E",
"bold": True,
},
}
],
}
],
}
],
}
],
}
content = {
"information_card_grid": {
"card_body": "**Digital ordering** supports quick, easy meals."
}
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
text_element = hydrated["components"][0]["elements"][0]["children"][0]
assert text_element["runs"] == [
{
"text": "Digital ordering",
"font": {
"size": 21.33,
"family": "HK Grotesk",
"color": "#1E1E1E",
"wrap": "word",
"bold": True,
},
},
{
"text": " supports quick, easy meals.",
"font": {
"size": 21.33,
"family": "HK Grotesk",
"color": "#1E1E1E",
"wrap": "word",
},
},
]
def test_apply_template_v2_content_to_ui_keeps_markdown_run_whitespace():
ui = {
"id": "layout-1",
"description": "Layout with markdown spacing edge cases.",
"components": [
{
"id": "left_header_block",
"description": "Header content component.",
"elements": [
{
"type": "text",
"decorative": False,
"name": "introductory_paragraph",
"runs": [
{
"text": "Old paragraph",
"font": {
"family": "Public Sans",
"size": 18.68,
"color": "#1A1A1A",
"line_height": 1.45,
"wrap": "word",
},
}
],
},
{
"type": "text",
"decorative": False,
"name": "compact_boundary",
"runs": [
{
"text": "Old compact text",
"font": {"family": "Inter", "size": 24},
}
],
},
],
}
],
}
content = {
"left_header_block": {
"introductory_paragraph": (
"Taco Bell's identity centers on **convenience, affordability, "
"bold flavors,** and a casual experience that feels playful, "
"accessible, and easy to customize."
),
"compact_boundary": "Use **guided**selling with *agentic*workflows",
}
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
elements = hydrated["components"][0]["elements"]
assert elements[0]["runs"] == [
{
"text": "Taco Bell's identity centers on ",
"font": {
"family": "Public Sans",
"size": 18.68,
"color": "#1A1A1A",
"line_height": 1.45,
"wrap": "word",
},
},
{
"text": "convenience, affordability, bold flavors,",
"font": {
"family": "Public Sans",
"size": 18.68,
"color": "#1A1A1A",
"line_height": 1.45,
"wrap": "word",
"bold": True,
},
},
{
"text": " and a casual experience that feels playful, accessible, and easy to customize.",
"font": {
"family": "Public Sans",
"size": 18.68,
"color": "#1A1A1A",
"line_height": 1.45,
"wrap": "word",
},
},
]
assert elements[1]["runs"] == [
{"text": "Use ", "font": {"family": "Inter", "size": 24}},
{
"text": "guided",
"font": {"family": "Inter", "size": 24, "bold": True},
},
{"text": "selling with ", "font": {"family": "Inter", "size": 24}},
{
"text": "agentic",
"font": {"family": "Inter", "size": 24, "italic": True},
},
{"text": "workflows", "font": {"family": "Inter", "size": 24}},
]
def test_template_v2_markdown_text_runs_keep_source_boundary_spaces():
runs = presentation_endpoint._template_v2_text_runs_from_markdown(
"Hello **how are you?** and",
{"font": {"family": "Inter", "size": 24}},
)
assert runs == [
{"text": "Hello ", "font": {"family": "Inter", "size": 24}},
{
"text": "how are you?",
"font": {"family": "Inter", "size": 24, "bold": True},
},
{"text": " and", "font": {"family": "Inter", "size": 24}},
]
def test_chat_template_v2_image_content_stores_prompt():
image = {
"type": "image",
"decorative": False,
"name": "hero_image",
"data": "/old-image.png",
"is_icon": False,
}
PresentationChatMemoryLayer._set_template_v2_element_value(
image,
{
"image_prompt": "Analytics dashboard",
"image_url": "/app_data/images/dashboard.png",
},
)
assert image["data"] == "/app_data/images/dashboard.png"
assert image["prompt"] == "Analytics dashboard"
icon = {
"type": "image",
"decorative": False,
"name": "status_icon",
"data": "/old-icon.svg",
"is_icon": True,
}
PresentationChatMemoryLayer._set_template_v2_element_value(
icon,
{
"icon_query": "success check",
"icon_url": "/icons/check.svg",
},
)
assert icon["data"] == "/icons/check.svg"
assert icon["prompt"] == "success check"
def test_apply_template_v2_content_to_ui_matches_repeated_content_lengths():
ui = {
"id": "layout-1",
"description": "Layout with repeated generated content.",
"components": [
{
"id": "repeated",
"description": "Repeated content component.",
"elements": [
{
"type": "flex",
"name": "flex_cards",
"min_children": 2,
"max_children": 3,
"children": [
{
"type": "group",
"name": "flex_card_1",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_1",
"runs": [{"text": "Flex one"}],
}
],
},
{
"type": "group",
"name": "flex_card_2",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_2",
"runs": [{"text": "Flex two"}],
}
],
},
{
"type": "group",
"name": "flex_card_3",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_3",
"runs": [{"text": "Flex three"}],
}
],
},
],
},
{
"type": "grid",
"name": "grid_cards",
"min_children": 2,
"max_children": 3,
"children": [
{
"type": "group",
"name": "grid_card_1",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_1",
"runs": [{"text": "Grid one"}],
}
],
},
{
"type": "group",
"name": "grid_card_2",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_2",
"runs": [{"text": "Grid two"}],
}
],
},
{
"type": "group",
"name": "grid_card_3",
"children": [
{
"type": "text",
"decorative": False,
"name": "title_3",
"runs": [{"text": "Grid three"}],
}
],
},
],
},
{
"type": "text-list",
"decorative": False,
"name": "bullets",
"min_items": 2,
"max_items": 3,
"items": [
[{"text": "Bullet one"}],
[{"text": "Bullet two"}],
[{"text": "Bullet three"}],
],
},
],
}
],
}
content = {
"repeated": {
"flex_cards": [
{"title": "Generated flex one"},
{"title": "Generated flex two"},
],
"grid_cards": [
{"title": "Generated grid one"},
{"title": "Generated grid two"},
],
"bullets": ["Generated bullet one", "Generated bullet two"],
}
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
elements = hydrated["components"][0]["elements"]
flex_children = elements[0]["children"]
grid_children = elements[1]["children"]
text_list_items = elements[2]["items"]
assert len(flex_children) == 2
assert flex_children[0]["children"][0]["runs"][0]["text"] == "Generated flex one"
assert flex_children[1]["children"][0]["runs"][0]["text"] == "Generated flex two"
assert len(grid_children) == 2
assert grid_children[0]["children"][0]["runs"][0]["text"] == "Generated grid one"
assert grid_children[1]["children"][0]["runs"][0]["text"] == "Generated grid two"
assert text_list_items == [
[{"text": "Generated bullet one"}],
[{"text": "Generated bullet two"}],
]
def test_apply_template_v2_content_to_ui_hydrates_direct_repeated_images():
ui = {
"id": "layout-1",
"components": [
{
"id": "gallery",
"elements": [
{
"type": "grid",
"name": "photo_panels",
"children": [
{
"type": "image",
"decorative": False,
"name": "gallery_photo_panel",
"data": "/static/images/replaceable_template_image.png",
"is_icon": False,
}
for _ in range(3)
],
}
],
}
],
}
content = {
"gallery": {
"photo_panels": [
{
"image_prompt": "Melting glacier",
"image_url": "/app_data/images/glacier.png",
},
{
"image_prompt": "Wildfire smoke",
"image_url": "/app_data/images/wildfire.png",
},
{
"image_prompt": "Flooded street",
"image_url": "/static/images/placeholder.jpg",
},
]
}
}
hydrated = presentation_endpoint._apply_template_v2_content_to_ui(ui, content)
images = hydrated["components"][0]["elements"][0]["children"]
assert [image["data"] for image in images] == [
"/app_data/images/glacier.png",
"/app_data/images/wildfire.png",
"/static/images/placeholder.jpg",
]
assert [image["prompt"] for image in images] == [
"Melting glacier",
"Wildfire smoke",
"Flooded street",
]
def test_chat_template_v2_content_hydrates_direct_repeated_images():
ui = {
"components": [
{
"id": "gallery",
"elements": [
{
"type": "grid",
"name": "photo_panels",
"children": [
{
"type": "image",
"decorative": False,
"name": "gallery_photo_panel",
"data": "/static/images/replaceable_template_image.png",
"is_icon": False,
}
],
}
],
}
]
}
PresentationChatMemoryLayer._apply_template_v2_content_to_ui(
ui,
{
"gallery": {
"photo_panels": [
{
"image_prompt": "Generated landscape",
"image_url": "/app_data/images/landscape.png",
}
]
}
},
)
image = ui["components"][0]["elements"][0]["children"][0]
assert image["data"] == "/app_data/images/landscape.png"
assert image["prompt"] == "Generated landscape"