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