547 lines
19 KiB
Python
547 lines
19 KiB
Python
from templates.v2.models.layouts import RawSlideLayout
|
|
from templates.v2.schema import (
|
|
extract_slide_schema_from_layout,
|
|
get_component_schema,
|
|
get_template_schema,
|
|
)
|
|
|
|
|
|
def test_extract_slide_schema_from_layout_extracts_editable_content():
|
|
layout = RawSlideLayout.model_validate(
|
|
{
|
|
"id": "content_slide",
|
|
"description": "Editable content with static decoration.",
|
|
"elements": [
|
|
{
|
|
"type": "rectangle",
|
|
"fill": {"color": "#ffffff"},
|
|
},
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "title",
|
|
"min_length": 4,
|
|
"max_length": 8,
|
|
"runs": [{"text": "Title"}],
|
|
},
|
|
{
|
|
"type": "text",
|
|
"decorative": True,
|
|
"name": "static_label",
|
|
"min_length": 1,
|
|
"max_length": 2,
|
|
"runs": [{"text": "A"}],
|
|
},
|
|
{
|
|
"type": "image",
|
|
"decorative": False,
|
|
"name": "hero_image",
|
|
"data": "/app_data/images/hero.png",
|
|
"is_icon": False,
|
|
},
|
|
{
|
|
"type": "container",
|
|
"child": {
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "caption",
|
|
"min_length": 2,
|
|
"max_length": 4,
|
|
"runs": [{"text": "Caption"}],
|
|
},
|
|
},
|
|
{
|
|
"type": "group",
|
|
"name": "details",
|
|
"children": [
|
|
{
|
|
"type": "text-list",
|
|
"decorative": False,
|
|
"name": "bullets",
|
|
"min_items": 2,
|
|
"max_items": 4,
|
|
"min_item_length": 5,
|
|
"max_item_length": 10,
|
|
"items": [[{"text": "First bullet"}]],
|
|
},
|
|
{
|
|
"type": "chart",
|
|
"decorative": False,
|
|
"name": "chart",
|
|
"chart_type": "bar",
|
|
"categories": ["Q1", "Q2"],
|
|
"series": [{"name": "Revenue", "values": [10, 12]}],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
)
|
|
|
|
assert extract_slide_schema_from_layout(layout) == {
|
|
"type": "object",
|
|
"properties": {
|
|
"title": {"type": "string", "minLength": 4, "maxLength": 8},
|
|
"hero_image": {
|
|
"type": "object",
|
|
"properties": {"prompt": {"type": "string"}},
|
|
"required": ["prompt"],
|
|
"additionalProperties": False,
|
|
},
|
|
"caption": {"type": "string", "minLength": 2, "maxLength": 4},
|
|
"details": {
|
|
"type": "object",
|
|
"properties": {
|
|
"bullets": {
|
|
"type": "array",
|
|
"minItems": 2,
|
|
"maxItems": 4,
|
|
"items": {
|
|
"type": "string",
|
|
"minLength": 5,
|
|
"maxLength": 10,
|
|
},
|
|
},
|
|
"chart": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"title": {"type": ["string", "null"]},
|
|
"title_color": {"type": ["string", "null"]},
|
|
"categories": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"maxItems": 24,
|
|
},
|
|
"series": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"values": {
|
|
"type": "array",
|
|
"items": {"type": "number"},
|
|
"maxItems": 24,
|
|
},
|
|
},
|
|
"required": ["name", "values"],
|
|
},
|
|
"maxItems": 12,
|
|
},
|
|
},
|
|
"required": ["categories", "series"],
|
|
},
|
|
},
|
|
"required": ["bullets", "chart"],
|
|
"additionalProperties": False,
|
|
},
|
|
},
|
|
"required": ["title", "hero_image", "caption", "details"],
|
|
"additionalProperties": False,
|
|
}
|
|
|
|
|
|
def test_extract_slide_schema_from_layout_collapses_repeated_children_to_array():
|
|
layout = RawSlideLayout.model_validate(
|
|
{
|
|
"id": "cards_slide",
|
|
"description": "Repeated card layout.",
|
|
"elements": [
|
|
{
|
|
"type": "flex",
|
|
"name": "cards",
|
|
"position": {"x": 0, "y": 0},
|
|
"size": {"width": 1280, "height": 240},
|
|
"direction": "row",
|
|
"min_children": 2,
|
|
"max_children": 4,
|
|
"children": [
|
|
{
|
|
"type": "group",
|
|
"name": "card_1",
|
|
"children": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "title_1",
|
|
"min_length": 3,
|
|
"max_length": 6,
|
|
"runs": [{"text": "One"}],
|
|
},
|
|
{
|
|
"type": "image",
|
|
"decorative": False,
|
|
"name": "icon_1",
|
|
"data": "/app_data/icons/icon-1.svg",
|
|
"is_icon": True,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"type": "group",
|
|
"name": "card_2",
|
|
"children": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "title_2",
|
|
"min_length": 3,
|
|
"max_length": 6,
|
|
"runs": [{"text": "Two"}],
|
|
},
|
|
{
|
|
"type": "image",
|
|
"decorative": False,
|
|
"name": "icon_2",
|
|
"data": "/app_data/icons/icon-2.svg",
|
|
"is_icon": True,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
],
|
|
}
|
|
)
|
|
|
|
assert extract_slide_schema_from_layout(layout) == {
|
|
"type": "object",
|
|
"properties": {
|
|
"cards": {
|
|
"type": "array",
|
|
"minItems": 2,
|
|
"maxItems": 4,
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"title": {
|
|
"type": "string",
|
|
"minLength": 3,
|
|
"maxLength": 6,
|
|
},
|
|
"icon": {
|
|
"type": "object",
|
|
"properties": {"query": {"type": "string"}},
|
|
"required": ["query"],
|
|
"additionalProperties": False,
|
|
},
|
|
},
|
|
"required": ["title", "icon"],
|
|
"additionalProperties": False,
|
|
},
|
|
}
|
|
},
|
|
"required": ["cards"],
|
|
"additionalProperties": False,
|
|
}
|
|
|
|
|
|
def test_get_component_schema_extracts_generated_component_content():
|
|
component = {
|
|
"id": "feature_card",
|
|
"description": "Reusable feature card component.",
|
|
"elements": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "headline",
|
|
"min_length": 4,
|
|
"max_length": 12,
|
|
},
|
|
{
|
|
"type": "image",
|
|
"decorative": False,
|
|
"name": "icon",
|
|
"data": "/app_data/icons/icon.svg",
|
|
"is_icon": True,
|
|
},
|
|
{
|
|
"type": "table",
|
|
"decorative": False,
|
|
"name": "metrics",
|
|
"min_columns": 2,
|
|
"max_columns": 4,
|
|
"min_rows": 1,
|
|
"max_rows": 3,
|
|
},
|
|
{
|
|
"type": "chart",
|
|
"decorative": False,
|
|
"name": "trend",
|
|
"chart_type": "line",
|
|
"categories": ["Q1", "Q2"],
|
|
"series": [{"name": "Revenue", "values": [10, 12]}],
|
|
},
|
|
],
|
|
}
|
|
|
|
assert get_component_schema(component) == {
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"type": "object",
|
|
"title": "feature_card",
|
|
"description": "Reusable feature card component.",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"headline": {
|
|
"type": "string",
|
|
"minLength": 4,
|
|
"maxLength": 12,
|
|
"title": "Headline",
|
|
"x-element-type": "text",
|
|
"x-element-path": "elements.0",
|
|
},
|
|
"icon": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"icon_query": {
|
|
"type": "string",
|
|
"description": "Search query for the replacement icon.",
|
|
}
|
|
},
|
|
"required": ["icon_query"],
|
|
"title": "Icon",
|
|
"x-element-type": "image",
|
|
"x-element-path": "elements.1",
|
|
},
|
|
"metrics": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"columns": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"minItems": 2,
|
|
"maxItems": 4,
|
|
},
|
|
"rows": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"minItems": 2,
|
|
"maxItems": 4,
|
|
},
|
|
"minItems": 1,
|
|
"maxItems": 3,
|
|
},
|
|
},
|
|
"required": ["columns", "rows"],
|
|
"title": "Metrics",
|
|
"x-element-type": "table",
|
|
"x-element-path": "elements.2",
|
|
},
|
|
"trend": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"title": {"type": ["string", "null"]},
|
|
"title_color": {"type": ["string", "null"]},
|
|
"categories": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"maxItems": 24,
|
|
},
|
|
"series": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"values": {
|
|
"type": "array",
|
|
"items": {"type": "number"},
|
|
"maxItems": 24,
|
|
},
|
|
},
|
|
"required": ["name", "values"],
|
|
},
|
|
"maxItems": 12,
|
|
},
|
|
},
|
|
"required": ["categories", "series"],
|
|
"title": "Trend",
|
|
"x-element-type": "chart",
|
|
"x-element-path": "elements.3",
|
|
},
|
|
},
|
|
"required": ["headline", "icon", "metrics", "trend"],
|
|
}
|
|
|
|
|
|
def test_get_component_schema_collapses_repeated_component_children_to_array():
|
|
component = {
|
|
"id": "card_grid",
|
|
"description": "Reusable card grid component.",
|
|
"elements": [
|
|
{
|
|
"type": "grid",
|
|
"name": "cards",
|
|
"min_children": 2,
|
|
"max_children": 4,
|
|
"children": [
|
|
{
|
|
"type": "group",
|
|
"name": "card_1",
|
|
"children": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "title_1",
|
|
"min_length": 3,
|
|
"max_length": 8,
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"type": "group",
|
|
"name": "card_2",
|
|
"children": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "title_2",
|
|
"min_length": 5,
|
|
"max_length": 12,
|
|
}
|
|
],
|
|
},
|
|
],
|
|
}
|
|
],
|
|
}
|
|
|
|
schema = get_component_schema(component)
|
|
|
|
assert schema is not None
|
|
assert schema["properties"]["cards"] == {
|
|
"type": "array",
|
|
"minItems": 2,
|
|
"maxItems": 4,
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"title": {
|
|
"type": "string",
|
|
"minLength": 3,
|
|
"maxLength": 12,
|
|
"title": "Title",
|
|
"x-element-type": "text",
|
|
}
|
|
},
|
|
"required": ["title"],
|
|
},
|
|
}
|
|
|
|
|
|
def test_get_template_schema_strips_component_metadata():
|
|
template = {
|
|
"layouts": [
|
|
{
|
|
"id": "intro",
|
|
"description": "Intro slide.",
|
|
"components": [
|
|
{
|
|
"id": "hero",
|
|
"description": "Hero image component.",
|
|
"elements": [
|
|
{
|
|
"type": "image",
|
|
"decorative": False,
|
|
"name": "photo",
|
|
"data": "/app_data/images/photo.png",
|
|
"is_icon": False,
|
|
}
|
|
],
|
|
}
|
|
],
|
|
}
|
|
]
|
|
}
|
|
|
|
assert get_template_schema(template) == {
|
|
"source_file": "template.json",
|
|
"layout_count": 1,
|
|
"layouts": [
|
|
{
|
|
"slide": 1,
|
|
"layout_id": "intro",
|
|
"schema": {
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"type": "object",
|
|
"title": "intro",
|
|
"description": "Intro slide.",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"hero": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"photo": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"image_prompt": {"type": "string"}
|
|
},
|
|
"required": ["image_prompt"],
|
|
}
|
|
},
|
|
"required": ["photo"],
|
|
}
|
|
},
|
|
"required": ["hero"],
|
|
},
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
def test_get_template_schema_numbers_duplicate_component_fields_from_zero():
|
|
template = {
|
|
"layouts": [
|
|
{
|
|
"id": "comparison",
|
|
"description": "Comparison slide.",
|
|
"components": [
|
|
{
|
|
"id": "metric_card",
|
|
"description": "Metric card component.",
|
|
"elements": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "value",
|
|
"min_length": 1,
|
|
"max_length": 8,
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "metric_card",
|
|
"description": "Metric card component.",
|
|
"elements": [
|
|
{
|
|
"type": "text",
|
|
"decorative": False,
|
|
"name": "value",
|
|
"min_length": 1,
|
|
"max_length": 8,
|
|
}
|
|
],
|
|
},
|
|
],
|
|
}
|
|
]
|
|
}
|
|
|
|
schema = get_template_schema(template)["layouts"][0]["schema"]
|
|
|
|
assert schema is not None
|
|
assert list(schema["properties"]) == ["metric_card_0", "metric_card_1"]
|
|
assert schema["required"] == ["metric_card_0", "metric_card_1"]
|
|
assert schema["properties"]["metric_card_0"] == schema["properties"]["metric_card_1"]
|