Refactoring DbEngine Fixing unit tests Fixing unit tests Fixing unit tests Refactored DbManager for datagrid Improving front end performance I can add new table Fixed sidebar closing when clicking on it Fix drag event rebinding, improve listener options, and add debug Prevent duplicate drag event bindings with a dataset flag and ensure consistent scrollbar functionality. Change wheel event listener to passive mode for better performance. Refactor function naming for consistency, and add debug logs for event handling. Refactor Datagrid bindings and default state handling. Updated Javascript to conditionally rebind Datagrid on specific events. Improved Python components by handling empty DataFrame cases and removing redundant code. Revised default state initialization in settings for better handling of mutable fields. Added Rowindex visualisation support Working on Debugger with own implementation of JsonViewer Working on JsonViewer.py Fixed unit tests Adding unit tests I can fold and unfold fixed unit tests Adding css for debugger Added tooltip management Adding debugger functionalities Refactor serializers and improve error handling in DB engine Fixed error where tables were overwritten I can display footer menu Working on footer. Refactoring how heights are managed Refactored scrollbars management Working on footer menu I can display footer menu + fixed unit tests Fixed unit tests Updated click management I can display aggregations in footers Added docker management Refactor input handling and improve config defaults Fixed scrollbars colors Refactored tooltip management Improved tooltip management Improving FilterAll
332 lines
11 KiB
Python
332 lines
11 KiB
Python
from collections import OrderedDict
|
|
|
|
import pandas as pd
|
|
import pytest
|
|
from fastcore.basics import NotStr
|
|
from fastcore.xml import to_xml
|
|
from fasthtml.components import *
|
|
|
|
from components.datagrid.DataGrid import DataGrid
|
|
from helpers import matches, search_elements_by_name, search_elements_by_path, extract_table_values, get_from_html, \
|
|
extract_popup_content, \
|
|
Empty, get_path_attributes, find_first_match, StartsWith, search_first_with_attribute, Contains
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_structure():
|
|
"""
|
|
A pytest fixture to provide a sample tree structure for testing.
|
|
"""
|
|
return Html(
|
|
Header(cls="first-class"),
|
|
Body(
|
|
"hello world",
|
|
Div(
|
|
Span(cls="highlight"),
|
|
Span(id="inner", name="child"),
|
|
id="content"),
|
|
),
|
|
Footer(),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("value, expected, expected_error", [
|
|
(Div(), "value",
|
|
"The types are different: <class 'fastcore.xml.FT'> != <class 'str'>\nactual=div((),{})\nexpected=value."),
|
|
(Div(), A(),
|
|
"The elements are different: 'div' != 'a'."),
|
|
(Div(Div()), Div(A()),
|
|
"Path 'div':\n\tThe elements are different: 'div' != 'a'."),
|
|
(Div(A(Span())), Div(A("element")),
|
|
"Path 'div.a':\n\tThe types are different: <class 'fastcore.xml.FT'> != <class 'str'>\nactual=span((),{})\nexpected=element."),
|
|
(Div(attr="one"), Div(attr="two"),
|
|
"Path 'div':\n\tThe values are different for 'attr' : 'one' != 'two'."),
|
|
(Div(A(attr="alpha")), Div(A(attr="beta")),
|
|
"Path 'div.a':\n\tThe values are different for 'attr' : 'alpha' != 'beta'."),
|
|
(Div(Div(), A()), Div(Div(), Span()),
|
|
"Path 'div':\n\tThe elements are different: 'a' != 'span'."),
|
|
(Div(A()), Div(A(attr="beta")),
|
|
"Path 'div.a':\n\tAttribute 'attr' is not found (with expected value: 'beta'). actual='{}'."),
|
|
(Div(id="one"), Div(id="two"),
|
|
"Path 'div#one':\n\tThe values are different for 'id' : 'one' != 'two'."),
|
|
(Div(id="same_id", attr="one"), Div(id="same_id", attr="two"),
|
|
"Path 'div#same_id':\n\tThe values are different for 'attr' : 'one' != 'two'."),
|
|
(Div(name="same_name", attr="one"), Div(name="same_name", attr="two"),
|
|
"Path 'div[name=same_name]':\n\tThe values are different for 'attr' : 'one' != 'two'."),
|
|
(Div(cls="same_class", attr="one"), Div(cls="same_class", attr="two"),
|
|
"Path 'div[class=same_class]':\n\tThe values are different for 'attr' : 'one' != 'two'."),
|
|
(Div(attr="value"), Div(Empty),
|
|
"Empty element expected, but found attributes {'attr': 'value'}."),
|
|
(Div(Div()), Div(Empty),
|
|
"Empty element expected, but found children (div((),{}),)."),
|
|
(Div(cls="a long attr"), Div(cls=StartsWith("different start")),
|
|
"Path 'div[class=a long attr]':\n\tAttribute 'class' does not start with 'different start': actual='a long attr', expected ='different start'."),
|
|
(Div(cls="a long attr"), Div(cls=Contains("not included")),
|
|
"Path 'div[class=a long attr]':\n\tAttribute 'class' does not contain 'not included': actual='a long attr', expected ='not included'."),
|
|
|
|
])
|
|
def test_matches_error_expected(value, expected, expected_error):
|
|
with pytest.raises(AssertionError) as error:
|
|
matches(value, expected)
|
|
|
|
assert error.value.args[0] == expected_error
|
|
|
|
|
|
@pytest.mark.parametrize("value, expected", [
|
|
(Div(), Div()),
|
|
(Div(A()), Div(A())),
|
|
(Div(id='do_not_validate'), Div(id='do_not_validate')),
|
|
(Div(A()), Div()), # children of actual are not selected
|
|
(Div(A(), Span(id="validate_please"), A(id="do_not_care")), Div(A(), Span(id="validate_please"))),
|
|
(Div(), Div(Empty)),
|
|
(Div(cls="a long attr"), Div(cls=StartsWith("a long"))),
|
|
(Div(cls="a long attr"), Div(cls=Contains("long"))),
|
|
])
|
|
def test_matches_success_expected(value, expected):
|
|
assert matches(value, expected)
|
|
|
|
|
|
def test_i_can_search_elements_by_name():
|
|
to_find = Table()
|
|
res = search_elements_by_name(to_find, "table")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(to_find))
|
|
res = search_elements_by_name(ft, "table")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "table")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(to_find, Span(to_find))
|
|
res = search_elements_by_name(ft, "table")
|
|
assert res == [to_find, to_find]
|
|
|
|
|
|
def test_i_can_search_not_str_by_name():
|
|
to_find = NotStr("Hello World")
|
|
|
|
res = search_elements_by_name(to_find, "NotStr")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(to_find))
|
|
res = search_elements_by_name(ft, "NotStr")
|
|
assert res == [to_find]
|
|
|
|
|
|
def test_i_can_search_elements_by_name_with_attr_and_exact_compare():
|
|
to_find = Table(attr="value")
|
|
|
|
res = search_elements_by_name(to_find, None, {"attr": "value"})
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, None, {"attr": "value"})
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "table", {"attr": "value"})
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "other", {"attr": "value"})
|
|
assert res == []
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "table", {"attr": "value2"})
|
|
assert res == []
|
|
|
|
ft = Div(Span(Table()))
|
|
res = search_elements_by_name(ft, "table", {"attr": "value"})
|
|
assert res == []
|
|
|
|
|
|
def test_i_can_search_elements_by_name_with_attr_and_contains_compare():
|
|
to_find = Table(attr="value1 value2 value3")
|
|
|
|
res = search_elements_by_name(to_find, None, {"attr": "value"}, comparison_method="contains")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, None, {"attr": "value"}, comparison_method="contains")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "table", {"attr": "value"}, comparison_method="contains")
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "other", {"attr": "value"}, comparison_method="contains")
|
|
assert res == []
|
|
|
|
ft = Div(Span(), Span(to_find))
|
|
res = search_elements_by_name(ft, "table", {"attr": "value4"}, comparison_method="contains")
|
|
assert res == []
|
|
|
|
ft = Div(Span(Table()))
|
|
res = search_elements_by_name(ft, "table", {"attr": "value"}, comparison_method="contains")
|
|
assert res == []
|
|
|
|
|
|
def test_i_can_select_path():
|
|
to_find = Table(attr="value")
|
|
|
|
res = search_elements_by_path(to_find, "table", None)
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(to_find))
|
|
res = search_elements_by_path(ft, "div.span.table", None)
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(to_find))
|
|
res = search_elements_by_path(ft, "span.table", None)
|
|
assert res == [to_find]
|
|
|
|
|
|
def test_i_can_select_path_with_attr():
|
|
to_find = Table(attr="value")
|
|
|
|
res = search_elements_by_path(to_find, "table", {"attr": "value"})
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(to_find))
|
|
res = search_elements_by_path(ft, "div.span.table", {"attr": "value"})
|
|
assert res == [to_find]
|
|
|
|
ft = Div(Span(to_find))
|
|
res = search_elements_by_path(ft, "span.table", {"attr": "value"})
|
|
assert res == [to_find]
|
|
|
|
res = search_elements_by_path(to_find, "span.table", {"attr": "value2"})
|
|
assert res == []
|
|
|
|
|
|
def test_i_can_extract_table_values_from_ft():
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id="testing_grid_id")
|
|
element = dg.__ft__()
|
|
|
|
assert extract_table_values(element) == OrderedDict({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': ["20", "25"]
|
|
})
|
|
|
|
assert extract_table_values(element, header=False) == [
|
|
["Alice", "20"],
|
|
["Bob", "25"]
|
|
]
|
|
|
|
|
|
def test_i_can_extract_table_values_from_html():
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id="testing_grid_id")
|
|
html = to_xml(dg.__ft__())
|
|
element = get_from_html(html)
|
|
|
|
assert extract_table_values(element) == OrderedDict({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': ["20", "25"]
|
|
})
|
|
|
|
assert extract_table_values(element, header=False) == [
|
|
["Alice", "20"],
|
|
["Bob", "25"]
|
|
]
|
|
|
|
|
|
def test_i_can_extract_popup_content_from_ft():
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob', 'Charlie'],
|
|
'Age': [20, 25, 30]
|
|
})
|
|
dg = DataGrid(df, id="testing_grid_id")
|
|
element = dg.mk_filter_popup_content("name")
|
|
|
|
assert extract_popup_content(element) == OrderedDict(
|
|
{'__filter_input__': '',
|
|
'Alice': False,
|
|
'Bob': False,
|
|
'Charlie': False,
|
|
}
|
|
)
|
|
|
|
|
|
def test_i_can_extract_popup_content_from_html():
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob', 'Charlie'],
|
|
'Age': [20, 25, 30]
|
|
})
|
|
dg = DataGrid(df, id="testing_grid_id")
|
|
html = to_xml(dg.mk_filter_popup_content("name"))
|
|
element = get_from_html(html)
|
|
|
|
assert extract_popup_content(element) == OrderedDict(
|
|
{'__filter_input__': '',
|
|
'Alice': False,
|
|
'Bob': False,
|
|
'Charlie': False,
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("path, expected_attributes", [
|
|
("div", {"tag": "div"}),
|
|
("div#my_id", {"tag": "div", "id": "my_id"}),
|
|
("div[class=my_class]", {"tag": "div", "class": "my_class"}),
|
|
("div#my_id[class=my_class]", {"tag": "div", "id": "my_id", "class": "my_class"}),
|
|
("div#my_id[a1=v1][a2=v2]", {"tag": "div", "id": "my_id", "a1": "v1", "a2": "v2"}),
|
|
("div#my_id[a1='v1']", {"tag": "div", "id": "my_id", "a1": "v1"}),
|
|
('div#my_id[a1="v1"]', {"tag": "div", "id": "my_id", "a1": "v1"}),
|
|
("div#my_id[a1='v-1']", {"tag": "div", "id": "my_id", "a1": "v-1"}),
|
|
("div#my_id[a1='v_1']", {"tag": "div", "id": "my_id", "a1": "v_1"}),
|
|
])
|
|
def test_i_can_get_path_attributes(path, expected_attributes):
|
|
assert get_path_attributes(path) == expected_attributes
|
|
|
|
|
|
def test_i_can_select_by_path():
|
|
# I can select the good one from a list
|
|
items = [Div(id='1'),
|
|
Div(id='2'),
|
|
Div(id='3')]
|
|
actual = find_first_match(items, "div#3")
|
|
assert actual == items[2]
|
|
|
|
# I can select using attribute
|
|
item = Div(Span(id="span_1"), Span(id="span_2"), id="div_1")
|
|
actual = find_first_match(item, "div.span#span_2")
|
|
assert actual == item.children[1]
|
|
|
|
# I can manage when no ft
|
|
items = [Div(Div("text")), Div(Div(Div(id="3"), id="2"), id="1")] # 'text' is not a ft, but there won't be any error
|
|
actual = find_first_match(items, "div.div.div")
|
|
assert actual.attrs["id"] == "3"
|
|
|
|
# None is returned when not found
|
|
assert find_first_match(item, "div.span#span_3") is None
|
|
|
|
|
|
@pytest.mark.parametrize("tag, attr, expected", [
|
|
("span", "class", ("span", "highlight")), # The tag and the attribute exist
|
|
("footer", "id", None), # The tag exists, but not the attribute
|
|
(None, "class", ("header", "first-class")), # First element with a given attribute
|
|
("p", "class", None), # The tag does not exist
|
|
("span", "id", ("span", "inner")), # Inner element
|
|
(None, "name", ("span", "child")), # Inner element
|
|
])
|
|
def test_i_can_search_first_with_attribute(tag, attr, expected, sample_structure):
|
|
result = search_first_with_attribute(sample_structure, tag, attr)
|
|
if expected is None:
|
|
assert result is None
|
|
else:
|
|
assert result.tag == expected[0]
|
|
assert attr in result.attrs
|
|
assert result.attrs[attr] == expected[1]
|