import ctypes
import os
import platform
import sys
import unittest
from ctypes import c_char_p, c_void_p, c_size_t, POINTER, c_ubyte
from typing import Optional
def _load_library():
system = platform.system()
if system == "Darwin":
lib_name = "libwebui_ffi.dylib"
elif system == "Windows":
lib_name = "webui_ffi.dll"
else:
lib_name = "libwebui_ffi.so"
script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.abspath(os.path.join(script_dir, "..", "..", "..", ".."))
search_paths = [
os.path.join(repo_root, "target", "debug", lib_name),
os.path.join(repo_root, "target", "release", lib_name),
]
for path in search_paths:
if os.path.exists(path):
return ctypes.cdll.LoadLibrary(path)
return ctypes.cdll.LoadLibrary(lib_name)
lib = _load_library()
lib.webui_handler_create.argtypes = []
lib.webui_handler_create.restype = c_void_p
lib.webui_handler_destroy.argtypes = [c_void_p]
lib.webui_handler_destroy.restype = None
lib.webui_handler_render.argtypes = [c_void_p, POINTER(c_ubyte), c_size_t, c_char_p, c_char_p, c_char_p]
lib.webui_handler_render.restype = c_void_p
lib.webui_render.argtypes = [c_char_p, c_char_p]
lib.webui_render.restype = c_void_p
lib.webui_free.argtypes = [c_void_p]
lib.webui_free.restype = None
lib.webui_last_error.argtypes = []
lib.webui_last_error.restype = c_char_p
def parse_and_render(html: str, data_json: str) -> str:
ptr = lib.webui_render(html.encode("utf-8"), data_json.encode("utf-8"))
if ptr is None or ptr == 0:
err = lib.webui_last_error()
err_msg = err.decode("utf-8") if err else "<no error>"
raise RuntimeError(f"webui_render failed: {err_msg}")
result = ctypes.cast(ptr, c_char_p).value.decode("utf-8")
lib.webui_free(ptr)
return result
def get_last_error() -> Optional[str]:
err = lib.webui_last_error()
if err is None:
return None
return err.decode("utf-8")
class TestParseAndRender(unittest.TestCase):
def test_simple_passthrough(self):
result = parse_and_render("<p>Hello</p>", "{}")
self.assertEqual(result, "<p>Hello</p>")
def test_signal_substitution(self):
result = parse_and_render("Hello, {{name}}!", '{"name":"WebUI"}')
self.assertEqual(result, "Hello, WebUI!")
def test_for_loop(self):
html = '<ul><for each="item in items"><li>{{item}}</li></for></ul>'
result = parse_and_render(html, '{"items":["a","b","c"]}')
self.assertEqual(result, "<ul><li>a</li><li>b</li><li>c</li></ul>")
def test_if_condition_true(self):
html = '<if condition="show"><p>Visible</p></if>'
result = parse_and_render(html, '{"show":true}')
self.assertEqual(result, "<p>Visible</p>")
def test_if_condition_false(self):
html = '<if condition="show"><p>Hidden</p></if>'
result = parse_and_render(html, '{"show":false}')
self.assertEqual(result, "")
def test_html_escaping(self):
result = parse_and_render(
"<div>{{content}}</div>",
'{"content":"<script>alert(\'xss\')</script>"}',
)
self.assertNotIn("<script>", result)
self.assertIn("<script>", result)
def test_raw_signal_unescaped(self):
result = parse_and_render(
"<div>{{{content}}}</div>", '{"content":"<b>bold</b>"}'
)
self.assertEqual(result, "<div><b>bold</b></div>")
def test_empty_data(self):
result = parse_and_render("<p>static</p>", "{}")
self.assertEqual(result, "<p>static</p>")
class TestErrorHandling(unittest.TestCase):
def test_null_html(self):
ptr = lib.webui_render(None, b"{}")
self.assertTrue(ptr is None or ptr == 0)
err = get_last_error()
self.assertIsNotNone(err)
self.assertIn("null", err)
def test_null_json(self):
ptr = lib.webui_render(b"<p>hi</p>", None)
self.assertTrue(ptr is None or ptr == 0)
self.assertIsNotNone(get_last_error())
def test_invalid_json(self):
ptr = lib.webui_render(b"<p>hi</p>", b"NOT JSON")
self.assertTrue(ptr is None or ptr == 0)
err = get_last_error()
self.assertIsNotNone(err)
self.assertIn("JSON", err)
def test_successful_call_clears_error(self):
lib.webui_render(b"<p>hi</p>", b"NOT JSON")
self.assertIsNotNone(get_last_error())
parse_and_render("<p>ok</p>", "{}")
self.assertIsNone(get_last_error())
class TestHandlerLifecycle(unittest.TestCase):
def test_create_and_destroy(self):
handler = lib.webui_handler_create()
self.assertIsNotNone(handler)
self.assertNotEqual(handler, 0)
lib.webui_handler_destroy(handler)
def test_destroy_null(self):
lib.webui_handler_destroy(None)
class TestFixtureFile(unittest.TestCase):
def test_fixture_renders_correctly(self):
fixtures_dir = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "fixtures"
)
with open(os.path.join(fixtures_dir, "simple.html")) as f:
html = f.read()
with open(os.path.join(fixtures_dir, "state.json")) as f:
data_json = f.read()
with open(os.path.join(fixtures_dir, "expected_output.html")) as f:
expected = f.read()
result = parse_and_render(html, data_json)
self.assertEqual(result, expected)
class TestFreeString(unittest.TestCase):
def test_free_null(self):
lib.webui_free(None)
if __name__ == "__main__":
unittest.main()