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