"""
Test auto_wrap feature of _build_component.
Tests the DAG-based automatic wrapping capability:
- Button with target_parent=Section → ButtonList[Button]
- Chip with target_parent=Section → ChipList[Chip]
- DecoratedText with target_parent=Section → no wrapping (direct child)
"""
import json
import os
import sys
import warnings
from pathlib import Path
import httpx
# Suppress SSL warnings for testing
warnings.filterwarnings("ignore", message="Unverified HTTPS request")
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
def test_auto_wrap_button():
"""Test auto_wrap for Button → ButtonList."""
from gchat.card_builder import SmartCardBuilderV2
builder = SmartCardBuilderV2()
print("=" * 60)
print("TEST 1: Button auto_wrap → ButtonList")
print("=" * 60)
# Button should be auto-wrapped in ButtonList
result = builder._build_component(
"Button",
{"text": "Click Me", "url": "https://example.com"},
wrap_with_key=True,
auto_wrap=True,
target_parent="Section",
)
# Should be wrapped in buttonList
assert (
"buttonList" in result
), f"Expected buttonList wrapper, got: {list(result.keys())}"
assert "buttons" in result["buttonList"], "Expected buttons array in buttonList"
buttons = result["buttonList"]["buttons"]
assert len(buttons) == 1, f"Expected 1 button, got {len(buttons)}"
print("✅ Button correctly wrapped in ButtonList")
print(f" Output: {json.dumps(result, indent=2)[:300]}")
return True
def test_auto_wrap_chip():
"""Test auto_wrap for Chip → ChipList."""
from gchat.card_builder import SmartCardBuilderV2
builder = SmartCardBuilderV2()
print("\n" + "=" * 60)
print("TEST 2: Chip auto_wrap → ChipList")
print("=" * 60)
result = builder._build_component(
"Chip",
{"label": "Tag 1", "url": "https://example.com"},
wrap_with_key=True,
auto_wrap=True,
target_parent="Section",
)
assert (
"chipList" in result
), f"Expected chipList wrapper, got: {list(result.keys())}"
print("✅ Chip correctly wrapped in ChipList")
print(f" Output: {json.dumps(result, indent=2)[:300]}")
return True
def test_no_wrap_decorated_text():
"""Test that DecoratedText doesn't get wrapped (direct child of Section)."""
from gchat.card_builder import SmartCardBuilderV2
builder = SmartCardBuilderV2()
print("\n" + "=" * 60)
print("TEST 3: DecoratedText - no wrapping needed")
print("=" * 60)
result = builder._build_component(
"DecoratedText",
{"text": "Direct child of Section", "wrapText": True},
wrap_with_key=True,
auto_wrap=True,
target_parent="Section",
)
# Should NOT be wrapped - decoratedText is a valid direct child
assert (
"decoratedText" in result
), f"Expected decoratedText, got: {list(result.keys())}"
assert "buttonList" not in result, "DecoratedText should not be wrapped"
print("✅ DecoratedText not wrapped (correct)")
print(f" Output: {json.dumps(result, indent=2)[:200]}")
return True
def test_auto_wrap_return_instance():
"""Test auto_wrap with return_instance=True."""
from gchat.card_builder import SmartCardBuilderV2
builder = SmartCardBuilderV2()
print("\n" + "=" * 60)
print("TEST 4: auto_wrap + return_instance=True")
print("=" * 60)
# Button with return_instance should return a ButtonList instance
instance = builder._build_component(
"Button",
{"text": "Instance Button", "url": "https://example.com"},
auto_wrap=True,
target_parent="Section",
return_instance=True,
)
instance_name = type(instance).__name__
assert (
instance_name == "ButtonList"
), f"Expected ButtonList instance, got: {instance_name}"
print(f"✅ Returned instance type: {instance_name}")
# Verify it can render
if hasattr(instance, "render"):
rendered = builder._convert_to_camel_case(instance.render())
print(f" Rendered: {json.dumps(rendered, indent=2)[:200]}")
return True
def test_full_card_with_auto_wrap():
"""Test building a complete card using auto_wrap."""
from gchat.card_builder import SmartCardBuilderV2
builder = SmartCardBuilderV2()
print("\n" + "=" * 60)
print("TEST 5: Full card with auto_wrap")
print("=" * 60)
# Build header
header = builder._build_component(
"CardHeader",
{"title": "Auto-Wrap Test Card", "subtitle": "Using DAG-based wrapping"},
return_instance=True,
)
# Build widgets - Button will be auto-wrapped
dt = builder._build_component(
"DecoratedText",
{"text": "Text goes directly in Section", "wrapText": True},
return_instance=True,
)
btn = builder._build_component(
"Button",
{"text": "Auto-Wrapped Button", "url": "https://example.com"},
auto_wrap=True,
target_parent="Section",
return_instance=True,
)
# Build Section with mixed children
section = builder._build_component(
"Section",
{"header": "Mixed Widgets"},
return_instance=True,
child_instances=[dt, btn],
)
# Build Card
card = builder._build_component(
"Card",
{},
return_instance=True,
child_instances=[header, section],
)
# Render
card_json = None
if card and hasattr(card, "render"):
card_json = builder._convert_to_camel_case(card.render())
# Verify structure
sections = card_json.get("card", {}).get("sections", [])
assert len(sections) == 1, f"Expected 1 section, got {len(sections)}"
widgets = sections[0].get("widgets", [])
assert len(widgets) == 2, f"Expected 2 widgets, got {len(widgets)}"
# First widget should be decoratedText
assert "decoratedText" in widgets[0], "First widget should be decoratedText"
# Second widget should be buttonList
assert "buttonList" in widgets[1], "Second widget should be buttonList"
print("✅ Card structure correct")
print(f" {len(widgets)} widgets: decoratedText, buttonList")
return card_json
def test_send_to_webhook():
"""Send the auto-wrapped card to webhook."""
import time
card_json = test_full_card_with_auto_wrap()
if not card_json:
print("❌ No card JSON to send")
return False
print("\n" + "=" * 60)
print("TEST 6: Send to Webhook")
print("=" * 60)
webhook_url = os.environ.get("TEST_CHAT_WEBHOOK")
if not webhook_url:
print("⚠️ TEST_CHAT_WEBHOOK not set, skipping webhook test")
return True
payload = {
"cardsV2": [
{
"cardId": f"auto_wrap_test_{int(time.time())}",
"card": card_json.get("card", card_json),
}
]
}
try:
response = httpx.post(webhook_url, json=payload, timeout=30, verify=False)
if response.status_code == 200:
print("✅ Card sent successfully!")
return True
else:
print(f"❌ Error: {response.status_code} - {response.text[:200]}")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
if __name__ == "__main__":
from dotenv import load_dotenv
load_dotenv()
results = {
"passed": 0,
"failed": 0,
}
tests = [
test_auto_wrap_button,
test_auto_wrap_chip,
test_no_wrap_decorated_text,
test_auto_wrap_return_instance,
test_send_to_webhook,
]
for test in tests:
try:
if test():
results["passed"] += 1
else:
results["failed"] += 1
except AssertionError as e:
print(f"❌ FAILED: {e}")
results["failed"] += 1
except Exception as e:
print(f"❌ ERROR: {e}")
results["failed"] += 1
print("\n" + "=" * 60)
print("SUMMARY")
print("=" * 60)
print(f"Passed: {results['passed']}")
print(f"Failed: {results['failed']}")