371 lines
12 KiB
Python
371 lines
12 KiB
Python
import pytest
|
|
|
|
from components.debugger.components.JsonViewer import *
|
|
from helpers import matches, span_icon, search_elements_by_name, extract_jsonviewer_node
|
|
|
|
JSON_VIEWER_INSTANCE_ID = "json_viewer"
|
|
ML_20 = "margin-left: 20px;"
|
|
CLS_PREFIX = "mmt-jsonviewer"
|
|
USER_ID = "user_id"
|
|
|
|
dn = DictNode
|
|
ln = ListNode
|
|
n = ValueNode
|
|
|
|
|
|
@pytest.fixture()
|
|
def json_viewer(session):
|
|
return JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, {})
|
|
|
|
|
|
@pytest.fixture()
|
|
def helper():
|
|
return JsonViewerHelper()
|
|
|
|
|
|
def jv_id(x):
|
|
return f"{JSON_VIEWER_INSTANCE_ID}-{x}"
|
|
|
|
|
|
@pytest.mark.parametrize("data, expected_node", [
|
|
({}, dn({}, jv_id(0), 0, {})),
|
|
([], ln([], jv_id(0), 0, [])),
|
|
(1, n(1)),
|
|
("value", n("value")),
|
|
(True, n(True)),
|
|
(None, n(None)),
|
|
([1, 2, 3], ln([1, 2, 3], jv_id(0), 0, [n(1), n(2), n(3)])),
|
|
({"a": 1, "b": 2}, dn({"a": 1, "b": 2}, jv_id(0), 0, {"a": n(1), "b": n(2)})),
|
|
({"a": [1, 2]}, dn({"a": [1, 2]}, jv_id(0), 0, {"a": ln([1, 2], jv_id(1), 1, [n(1), n(2)])})),
|
|
([{"a": [1, 2]}],
|
|
ln([{"a": [1, 2]}], jv_id(0), 0, [dn({"a": [1, 2]}, jv_id(1), 1, {"a": ln([1, 2], jv_id(2), 2, [n(1), n(2)])})]))
|
|
])
|
|
def test_i_can_create_node(data, expected_node):
|
|
json_viewer_ = JsonViewer(None, JSON_VIEWER_INSTANCE_ID, None, USER_ID, data)
|
|
assert json_viewer_.node == expected_node
|
|
|
|
|
|
def test_i_can_render(json_viewer):
|
|
actual = json_viewer.__ft__()
|
|
expected = Div(
|
|
Div(Div(id=f"{jv_id('0')}"), id=f"{jv_id('root')}"), # root debug
|
|
cls=f"{CLS_PREFIX}",
|
|
id=JSON_VIEWER_INSTANCE_ID)
|
|
|
|
assert matches(actual, expected)
|
|
|
|
|
|
@pytest.mark.parametrize("value, expected_inner", [
|
|
("hello world", Span('"hello world"', cls=f"{CLS_PREFIX}-string")),
|
|
(1, Span("1", cls=f"{CLS_PREFIX}-number")),
|
|
(True, Span("true", cls=f"{CLS_PREFIX}-bool")),
|
|
(False, Span("false", cls=f"{CLS_PREFIX}-bool")),
|
|
(None, Span("null", cls=f"{CLS_PREFIX}-null")),
|
|
])
|
|
def test_i_can_render_simple_value(session, value, expected_inner):
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
actual = jsonv.__ft__()
|
|
to_compare = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
expected = Div(
|
|
|
|
Div(
|
|
None, # no folding
|
|
None, # # 'key :' is missing for the first node
|
|
expected_inner,
|
|
style=ML_20),
|
|
|
|
id=f"{jv_id('root')}")
|
|
|
|
assert matches(to_compare, expected)
|
|
|
|
|
|
def test_i_can_render_expanded_list_node(session):
|
|
value = [1, "hello", True]
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
# Force expansion of the node
|
|
jsonv.set_folding_mode("expand")
|
|
|
|
actual = jsonv.__ft__()
|
|
to_compare = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
to_compare = to_compare.children[0] # I want to compare what is inside the div
|
|
|
|
expected_inner = Span("[",
|
|
Div(None, Span("0 : "), Span('1', cls=f"{CLS_PREFIX}-number"), style=ML_20),
|
|
Div(None, Span("1 : "), Span('"hello"', cls=f"{CLS_PREFIX}-string"), style=ML_20),
|
|
Div(None, Span("2 : "), Span('true', cls=f"{CLS_PREFIX}-bool"), style=ML_20),
|
|
Div("]"))
|
|
|
|
expected = Div(
|
|
span_icon("expanded"),
|
|
None, # 'key :' is missing for the first node
|
|
expected_inner,
|
|
style=ML_20,
|
|
id=jv_id(0))
|
|
|
|
assert matches(to_compare, expected)
|
|
|
|
|
|
def test_i_can_render_expanded_dict_node(session):
|
|
value = {"a": 1, "b": "hello", "c": True}
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
# Force expansion of the node
|
|
jsonv.set_folding_mode("expand")
|
|
|
|
actual = jsonv.__ft__()
|
|
to_compare = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
to_compare = to_compare.children[0] # I want to compare what is inside the div
|
|
|
|
expected_inner = Span("{",
|
|
Div(None, Span("a : "), Span('1', cls=f"{CLS_PREFIX}-number"), style=ML_20),
|
|
Div(None, Span("b : "), Span('"hello"', cls=f"{CLS_PREFIX}-string"), style=ML_20),
|
|
Div(None, Span("c : "), Span('true', cls=f"{CLS_PREFIX}-bool"), style=ML_20),
|
|
Div("}"))
|
|
|
|
expected = Div(
|
|
span_icon("expanded"),
|
|
None, # 'key :' is missing for the first node
|
|
expected_inner,
|
|
style=ML_20,
|
|
id=jv_id(0))
|
|
|
|
assert matches(to_compare, expected)
|
|
|
|
|
|
def test_i_can_render_expanded_list_of_dict_node(session):
|
|
value = [{"a": 1, "b": "hello"}]
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
# Force expansion of all nodes
|
|
jsonv.set_folding_mode("expand")
|
|
|
|
actual = jsonv.__ft__()
|
|
to_compare = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
to_compare = to_compare.children[0] # I want to compare what is inside the div
|
|
|
|
expected_inner = Span("[",
|
|
|
|
Div(span_icon("expanded"),
|
|
Span("0 : "),
|
|
Span("{",
|
|
Div(None, Span("a : "), Span('1', cls=f"{CLS_PREFIX}-number"), style=ML_20),
|
|
Div(None, Span("b : "), Span('"hello"', cls=f"{CLS_PREFIX}-string"), style=ML_20),
|
|
Div("}")),
|
|
style=ML_20,
|
|
id=f"{jv_id(1)}"),
|
|
|
|
Div("]"))
|
|
|
|
expected = Div(
|
|
span_icon("expanded"),
|
|
None, # 'key :' is missing for the first node
|
|
expected_inner,
|
|
style=ML_20,
|
|
id=jv_id(0))
|
|
|
|
assert matches(to_compare, expected)
|
|
|
|
|
|
def test_render_with_collapse_folding_mode(session):
|
|
# Create a nested structure to test collapse rendering
|
|
value = {"a": [1, 2, 3], "b": {"x": "y", "z": True}}
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
|
|
# Ensure folding mode is set to collapse (should be default)
|
|
jsonv.set_folding_mode("collapse")
|
|
assert jsonv.get_folding_mode() == "collapse"
|
|
|
|
actual = jsonv.__ft__()
|
|
root_div = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
|
|
# In collapse mode, the first level should show collapsed representations
|
|
# The dict node should be rendered as "{...}"
|
|
first_level_div = root_div.children[0]
|
|
|
|
# Verify that the first level shows a collapsed view
|
|
expected_first_level = Div(
|
|
span_icon("collapsed"),
|
|
None, # No key for the root node
|
|
Span("{...}", id=jv_id(0)),
|
|
style=ML_20,
|
|
id=jv_id(0)
|
|
)
|
|
|
|
assert matches(first_level_div, expected_first_level)
|
|
|
|
|
|
def test_render_with_specific_node_expanded_in_collapse_mode(session):
|
|
# Create a nested structure to test mixed collapse/expand rendering
|
|
value = {"a": [1, 2, 3], "b": {"x": "y", "z": True}}
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
|
|
# Ensure folding mode is set to collapse
|
|
jsonv.set_folding_mode(FoldingMode.COLLAPSE)
|
|
|
|
# Manually expand the root node
|
|
jsonv.set_node_folding(f"{JSON_VIEWER_INSTANCE_ID}-0", "expand")
|
|
|
|
actual = jsonv.__ft__()
|
|
root_div = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
first_level_div = root_div.children[0]
|
|
|
|
as_node = extract_jsonviewer_node(first_level_div)
|
|
|
|
# The first level should now be expanded but children should be collapsed
|
|
assert as_node.is_expanded is True
|
|
|
|
# Find div with "a" key
|
|
a_node = as_node.find("a")
|
|
b_node = as_node.find("b")
|
|
|
|
# Verify that both a and b nodes show collapsed representations
|
|
assert a_node is not None
|
|
assert b_node is not None
|
|
|
|
assert a_node.is_expanded is False
|
|
assert a_node.text_value() == "[...]"
|
|
|
|
assert b_node.is_expanded is False
|
|
assert b_node.text_value() == "{...}"
|
|
|
|
|
|
def test_multiple_folding_levels_in_collapse_mode(session):
|
|
# Create a deeply nested structure
|
|
value = {"level1": {"level2": {"level3": [1, 2, 3]}}}
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
|
|
# Set folding mode to collapse
|
|
jsonv.set_folding_mode(FoldingMode.COLLAPSE)
|
|
|
|
# Expand the first two levels
|
|
jsonv.set_node_folding(f"{jsonv.get_id()}-0", FoldingMode.EXPAND) # top level
|
|
jsonv.set_node_folding(f"{jsonv.get_id()}-1", FoldingMode.EXPAND) # level1
|
|
jsonv.set_node_folding(f"{jsonv.get_id()}-2", FoldingMode.EXPAND) # level2
|
|
|
|
actual = jsonv.__ft__()
|
|
root_div = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
|
|
# Navigate to level3 to verify it's still collapsed
|
|
first_level_div = root_div.children[0]
|
|
first_level_node = extract_jsonviewer_node(first_level_div)
|
|
assert first_level_node.is_expanded is True
|
|
|
|
# Find level2 in the rendered structure
|
|
level2_node = first_level_node.find("level1.level2")
|
|
assert level2_node is not None
|
|
assert level2_node.is_expanded is True
|
|
|
|
# Find level3 in the rendered structure
|
|
level3_node = level2_node.find("level3")
|
|
assert level3_node is not None
|
|
assert level3_node.is_expanded is False
|
|
assert level3_node.text_value() == "[...]"
|
|
|
|
|
|
def test_toggle_between_folding_modes(session):
|
|
value = {"a": [1, 2, 3], "b": {"x": "y"}}
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, value)
|
|
|
|
# Start with collapse mode
|
|
jsonv.set_folding_mode("collapse")
|
|
|
|
# Expand specific node
|
|
jsonv.set_node_folding(f"{JSON_VIEWER_INSTANCE_ID}-0", "expand")
|
|
|
|
# Verify node is in tracked nodes (exceptions to collapse mode)
|
|
assert f"{JSON_VIEWER_INSTANCE_ID}-0" in jsonv._nodes_to_track
|
|
|
|
# Now switch to expand mode
|
|
jsonv.set_folding_mode("expand")
|
|
|
|
# Tracked nodes should be cleared
|
|
assert len(jsonv._nodes_to_track) == 0
|
|
|
|
# Collapse specific node
|
|
jsonv.set_node_folding(f"{JSON_VIEWER_INSTANCE_ID}-0", "collapse")
|
|
|
|
# Verify node is in tracked nodes (exceptions to expand mode)
|
|
assert f"{JSON_VIEWER_INSTANCE_ID}-0" in jsonv._nodes_to_track
|
|
|
|
# Render and verify the output
|
|
actual = jsonv.__ft__()
|
|
root_div = search_elements_by_name(actual, "div", attrs={"id": f"{jv_id('root')}"})[0]
|
|
first_level_div = root_div.children[0]
|
|
|
|
# First level should be collapsed in an otherwise expanded tree
|
|
as_node = extract_jsonviewer_node(first_level_div)
|
|
assert as_node.is_expanded is False
|
|
assert as_node.text_value() == "{...}"
|
|
|
|
|
|
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, None, USER_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)
|
|
|
|
|
|
def test_folding_mode_operations(session):
|
|
jsonv = JsonViewer(session, JSON_VIEWER_INSTANCE_ID, None, USER_ID, {"a": [1, 2, 3]})
|
|
|
|
# Check default folding mode
|
|
assert jsonv.get_folding_mode() == "collapse"
|
|
|
|
# Change folding mode
|
|
jsonv.set_folding_mode("expand")
|
|
assert jsonv.get_folding_mode() == "expand"
|
|
|
|
# Set node folding
|
|
node_id = f"{JSON_VIEWER_INSTANCE_ID}-0"
|
|
jsonv.set_node_folding(node_id, "collapse")
|
|
|
|
# Node should be in tracked nodes since it differs from the default mode
|
|
assert node_id in jsonv._nodes_to_track
|
|
|
|
# Restore to match default mode
|
|
jsonv.set_node_folding(node_id, "expand")
|
|
assert node_id not in jsonv._nodes_to_track
|
|
|
|
|
|
@pytest.mark.parametrize("input_value, expected_output", [
|
|
('Hello World', '"Hello World"'), # No quotes in input
|
|
('Hello "World"', "'Hello \"World\"'"), # Contains double quotes
|
|
("Hello 'World'", '"Hello \'World\'"'), # Contains single quotes
|
|
('Hello "World" and \'Universe\'', '"Hello \\"World\\" and \'Universe\'"'), # both single and double quotes
|
|
('', '""'), # Empty string
|
|
])
|
|
def test_add_quotes(input_value, expected_output):
|
|
result = JsonViewer.add_quotes(input_value)
|
|
assert result == expected_output
|
|
|
|
|
|
def test_helper_is_sha256(helper):
|
|
# Valid SHA256
|
|
assert helper.is_sha256("a" * 64)
|
|
assert helper.is_sha256("0123456789abcdef" * 4)
|
|
assert helper.is_sha256("0123456789ABCDEF" * 4)
|
|
|
|
# Invalid cases
|
|
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
|