Added Hooks implementation
This commit is contained in:
159
tests/test_hooks.py
Normal file
159
tests/test_hooks.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import pytest
|
||||
|
||||
from components.jsonviewer.hooks import (
|
||||
HookContext, EventType, Hook, HookManager,
|
||||
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
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from components.jsonviewer.components.JsonViewer import *
|
||||
from components.jsonviewer.hooks import HookBuilder
|
||||
from helpers import matches, span_icon, search_elements_by_name, extract_jsonviewer_node
|
||||
|
||||
JSON_VIEWER_INSTANCE_ID = "json_viewer"
|
||||
@@ -296,30 +297,39 @@ def test_toggle_between_folding_modes(session):
|
||||
|
||||
|
||||
def test_custom_hook_rendering(session, helper):
|
||||
# Define a custom hook for testing
|
||||
def custom_predicate(key, node, h):
|
||||
return isinstance(node.value, str) and node.value == "custom_hook_test"
|
||||
|
||||
def custom_renderer(key, node, h):
|
||||
return Span("CUSTOM_HOOK_RENDER", cls="custom-hook-class")
|
||||
|
||||
hooks = [(custom_predicate, custom_renderer)]
|
||||
|
||||
# Create JsonViewer with the custom hook
|
||||
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, "custom_hook_test", hooks=hooks)
|
||||
|
||||
actual = jsonv.__ft__()
|
||||
to_compare = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
||||
|
||||
expected = Div(
|
||||
Div(
|
||||
None,
|
||||
None,
|
||||
Span("CUSTOM_HOOK_RENDER", cls="custom-hook-class"),
|
||||
style=ML_20),
|
||||
id=f"{jv_id('root')}")
|
||||
|
||||
assert matches(to_compare, expected)
|
||||
# Define a custom condition to check if the value is "custom_hook_test"
|
||||
def custom_condition(context):
|
||||
return isinstance(context.node.value, str) and context.node.value == "custom_hook_test"
|
||||
|
||||
# Define a custom executor to render the desired output
|
||||
def custom_renderer(context):
|
||||
return Span("CUSTOM_HOOK_RENDER", cls="custom-hook-class")
|
||||
|
||||
# Build the hook using HookBuilder
|
||||
hook = (HookBuilder()
|
||||
.on_render()
|
||||
.when_custom(custom_condition)
|
||||
.execute(custom_renderer))
|
||||
|
||||
# Create a JsonViewer with the new hook
|
||||
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, "custom_hook_test", hooks=[hook])
|
||||
|
||||
# Actual rendered output
|
||||
actual = jsonv.__ft__()
|
||||
to_compare = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
||||
|
||||
# Expected rendered output
|
||||
expected = Div(
|
||||
Div(
|
||||
None,
|
||||
None,
|
||||
Span("CUSTOM_HOOK_RENDER", cls="custom-hook-class"),
|
||||
style=ML_20),
|
||||
id=f"{jv_id('root')}"
|
||||
)
|
||||
|
||||
# Assert that the actual output matches the expected output
|
||||
assert matches(to_compare, expected)
|
||||
|
||||
|
||||
def test_folding_mode_operations(session):
|
||||
@@ -366,4 +376,4 @@ def test_helper_is_sha256(helper):
|
||||
assert not helper.is_sha256("a" * 63) # Too short
|
||||
assert not helper.is_sha256("a" * 65) # Too long
|
||||
assert not helper.is_sha256("g" * 64) # Invalid character
|
||||
assert not helper.is_sha256("test") # Not a hash
|
||||
assert not helper.is_sha256("test") # Not a hash
|
||||
Reference in New Issue
Block a user