229 lines
9.1 KiB
Python
229 lines
9.1 KiB
Python
import pytest
|
|
|
|
from components.jsonviewer.hooks import (
|
|
HookContext, EventType, Hook, HookManager, HookBuilder,
|
|
WhenLongText, WhenEditable, WhenType, WhenKey, WhenPath, WhenValue,
|
|
CompositeCondition
|
|
)
|
|
|
|
|
|
# HookContext test helper
|
|
def create_mock_context(value=None, key=None, json_path=None, parent_node=None, node_type=None, children=None):
|
|
"""Helper to create a mock HookContext for testing."""
|
|
|
|
class Node:
|
|
def __init__(self, value, node_type=None, children=None):
|
|
self.value = value
|
|
self.__class__.__name__ = node_type or "MockNode"
|
|
self.children = children or []
|
|
|
|
mock_node = Node(value, node_type=node_type, children=children)
|
|
return HookContext(key=key, node=mock_node, helper=None, jsonviewer=None, json_path=json_path,
|
|
parent_node=parent_node)
|
|
|
|
|
|
# ================
|
|
# Test Conditions
|
|
# ================
|
|
|
|
@pytest.mark.parametrize("text, threshold, expected", [
|
|
("This is a very long text." * 10, 50, True), # Long text, above threshold
|
|
("Short text", 50, False), # Short text, below threshold
|
|
])
|
|
def test_i_can_detect_long_text(text, threshold, expected):
|
|
context = create_mock_context(value=text)
|
|
condition = WhenLongText(threshold=threshold)
|
|
assert condition.evaluate(context) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("json_path, editable_paths, editable_types, node_value, is_leaf, expected", [
|
|
("root.editable.value", ["root.editable.value"], None, "Editable value", True, True), # Editable path matches
|
|
("root.not_editable.value", ["root.editable.value"], None, "Editable value", True, False),
|
|
# Editable path does not match
|
|
("root.editable.numeric", [], [int], 10, True, True), # Type is editable (int)
|
|
("root.editable.string", [], [int], "Non-editable value", True, False) # Type is not editable
|
|
])
|
|
def test_i_can_detect_editable(json_path, editable_paths, editable_types, node_value, is_leaf, expected):
|
|
context = create_mock_context(value=node_value, json_path=json_path)
|
|
context.is_leaf_node = lambda: is_leaf # Mock is_leaf_node behavior
|
|
condition = WhenEditable(editable_paths=editable_paths, editable_types=editable_types)
|
|
assert condition.evaluate(context) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("node_value, target_type, expected", [
|
|
(123, int, True), # Matches target type
|
|
("String value", int, False) # Does not match target type
|
|
])
|
|
def test_i_can_detect_type_match(node_value, target_type, expected):
|
|
context = create_mock_context(value=node_value)
|
|
condition = WhenType(target_type=target_type)
|
|
assert condition.evaluate(context) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("key, key_pattern, expected", [
|
|
("target_key", "target_key", True), # Exact match
|
|
("target_key", lambda k: k.startswith("target"), True), # Callable match
|
|
("wrong_key", "target_key", False) # Pattern does not match
|
|
])
|
|
def test_i_can_match_key(key, key_pattern, expected):
|
|
context = create_mock_context(key=key)
|
|
condition = WhenKey(key_pattern=key_pattern)
|
|
assert condition.evaluate(context) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("json_path, path_pattern, expected", [
|
|
("root.items[0].name", r"root\.items\[\d+\]\.name", True), # Matches pattern
|
|
("root.invalid_path", r"root\.items\[\d+\]\.name", False) # Does not match
|
|
])
|
|
def test_i_can_match_path(json_path, path_pattern, expected):
|
|
context = create_mock_context(json_path=json_path)
|
|
condition = WhenPath(path_pattern=path_pattern)
|
|
assert condition.evaluate(context) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("value, target_value, predicate, expected", [
|
|
(123, 123, None, True), # Direct match
|
|
(123, 456, None, False), # Direct mismatch
|
|
(150, None, lambda v: v > 100, True), # Satisfies predicate
|
|
(50, None, lambda v: v > 100, False), # Does not satisfy predicate
|
|
])
|
|
def test_i_can_detect_value(value, target_value, predicate, expected):
|
|
context = create_mock_context(value=value)
|
|
condition = WhenValue(target_value=target_value, predicate=predicate)
|
|
assert condition.evaluate(context) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("value, conditions, operator, expected", [
|
|
(200, [WhenValue(predicate=lambda v: v > 100), WhenType(target_type=int)], "AND", True),
|
|
# Both conditions pass (AND)
|
|
(200, [WhenValue(predicate=lambda v: v > 100), WhenType(target_type=str)], "AND", False),
|
|
# One condition fails (AND)
|
|
(200, [WhenValue(predicate=lambda v: v > 100), WhenType(target_type=str)], "OR", True),
|
|
# At least one passes (OR)
|
|
(50, [], "AND", True), # No conditions (default True for AND/OR)
|
|
])
|
|
def test_i_can_combine_conditions(value, conditions, operator, expected):
|
|
context = create_mock_context(value=value)
|
|
composite = CompositeCondition(conditions=conditions, operator=operator)
|
|
assert composite.evaluate(context) == expected
|
|
|
|
|
|
# ================
|
|
# Test Hooks
|
|
# ================
|
|
|
|
@pytest.mark.parametrize("event_type, actual_event, threshold, text, expected", [
|
|
(EventType.RENDER, EventType.RENDER, 10, "Long text" * 10, True), # Event matches, meets condition
|
|
(EventType.RENDER, EventType.CLICK, 10, "Long text" * 10, False), # Event mismatch
|
|
])
|
|
def test_i_can_match_hook(event_type, actual_event, threshold, text, expected):
|
|
context = create_mock_context(value=text)
|
|
condition = WhenLongText(threshold=threshold)
|
|
hook = Hook(event_type=event_type, conditions=[condition], executor=lambda ctx: "Executed")
|
|
|
|
assert hook.matches(event_type=actual_event, context=context) == expected
|
|
|
|
|
|
# ================
|
|
# Test HookManager
|
|
# ================
|
|
|
|
def test_i_can_execute_hooks_in_manager():
|
|
hook_manager = HookManager()
|
|
|
|
# Add hooks
|
|
hook1 = Hook(EventType.RENDER, conditions=[], executor=lambda ctx: "Render Executed")
|
|
hook2 = Hook(EventType.CLICK, conditions=[], executor=lambda ctx: "Click Executed")
|
|
|
|
hook_manager.add_hook(hook1)
|
|
hook_manager.add_hook(hook2)
|
|
|
|
context = create_mock_context()
|
|
render_results = hook_manager.execute_hooks(event_type=EventType.RENDER, context=context)
|
|
click_results = hook_manager.execute_hooks(event_type=EventType.CLICK, context=context)
|
|
|
|
assert len(render_results) == 1
|
|
assert render_results[0] == "Render Executed"
|
|
|
|
assert len(click_results) == 1
|
|
assert click_results[0] == "Click Executed"
|
|
|
|
|
|
def test_i_can_clear_hooks_in_manager():
|
|
hook_manager = HookManager()
|
|
|
|
hook_manager.add_hook(Hook(EventType.RENDER, conditions=[], executor=lambda ctx: "Render"))
|
|
assert len(hook_manager.hooks) == 1
|
|
|
|
hook_manager.clear_hooks()
|
|
assert len(hook_manager.hooks) == 0
|
|
|
|
|
|
# ================
|
|
# Test HookBuilder with Callable Conditions
|
|
# ================
|
|
|
|
def test_i_can_use_callable_with_when_custom():
|
|
"""Test that when_custom() accepts callable predicates"""
|
|
|
|
# Define a simple callable condition
|
|
def custom_condition(context):
|
|
return isinstance(context.get_value(), str) and context.get_value().startswith("CUSTOM_")
|
|
|
|
# Create hook using callable condition
|
|
hook = (HookBuilder()
|
|
.on_render()
|
|
.when_custom(custom_condition)
|
|
.execute(lambda ctx: "Custom hook executed"))
|
|
|
|
# Test with matching context
|
|
matching_context = create_mock_context(value="CUSTOM_test_value")
|
|
assert hook.matches(EventType.RENDER, matching_context) == True
|
|
assert hook.execute(matching_context) == "Custom hook executed"
|
|
|
|
# Test with non-matching context
|
|
non_matching_context = create_mock_context(value="regular_value")
|
|
assert hook.matches(EventType.RENDER, non_matching_context) == False
|
|
|
|
|
|
def test_i_can_use_lambda_with_when_custom():
|
|
"""Test that when_custom() accepts lambda expressions"""
|
|
|
|
# Create hook using lambda condition
|
|
hook = (HookBuilder()
|
|
.on_render()
|
|
.when_custom(lambda ctx: ctx.key == "special" and isinstance(ctx.get_value(), int) and ctx.get_value() > 100)
|
|
.execute(lambda ctx: f"Special value: {ctx.get_value()}"))
|
|
|
|
# Test with matching context
|
|
matching_context = create_mock_context(value=150, key="special")
|
|
assert hook.matches(EventType.RENDER, matching_context) == True
|
|
assert hook.execute(matching_context) == "Special value: 150"
|
|
|
|
# Test with non-matching contexts
|
|
wrong_key_context = create_mock_context(value=150, key="normal")
|
|
assert hook.matches(EventType.RENDER, wrong_key_context) == False
|
|
|
|
wrong_value_context = create_mock_context(value=50, key="special")
|
|
assert hook.matches(EventType.RENDER, wrong_value_context) == False
|
|
|
|
|
|
@pytest.mark.parametrize("value, key, json_path, expected", [
|
|
("CUSTOM_hook_test", "test_key", "root.test", True), # Matches callable condition
|
|
("regular_text", "test_key", "root.test", False), # Doesn't match callable condition
|
|
(123, "test_key", "root.test", False), # Wrong type
|
|
])
|
|
def test_callable_condition_evaluation(value, key, json_path, expected):
|
|
"""Test callable condition evaluation with different inputs"""
|
|
|
|
def custom_callable_condition(context):
|
|
return isinstance(context.get_value(), str) and context.get_value().startswith("CUSTOM_")
|
|
|
|
hook = (HookBuilder()
|
|
.on_render()
|
|
.when_custom(custom_callable_condition)
|
|
.execute(lambda ctx: "Executed"))
|
|
|
|
context = create_mock_context(value=value, key=key, json_path=json_path)
|
|
assert hook.matches(EventType.RENDER, context) == expected
|