2106 lines
62 KiB
Python
2106 lines
62 KiB
Python
from collections import OrderedDict
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
|
|
from components.datagrid.DataGrid import *
|
|
from helpers import matches, extract_popup_content, extract_table_values, Empty, to_array, search_elements_by_name, get_selected, \
|
|
get_context_menu
|
|
|
|
TEST_GRID_ID = "testing_grid_id"
|
|
|
|
|
|
@pytest.fixture()
|
|
def dg():
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
return DataGrid(df, id=TEST_GRID_ID)
|
|
|
|
|
|
@pytest.fixture()
|
|
def dg2():
|
|
"""
|
|
More elements
|
|
:return:
|
|
"""
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob', 'Charlie', 'David'],
|
|
'Age': [20, 25, 30, 35],
|
|
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston'],
|
|
'Student': [True, False, True, False],
|
|
'Date': [datetime(2020, 1, 10), datetime(2022, 10, 1), datetime(2007, 1, 1), datetime(2000, 12, 31), ]
|
|
})
|
|
return DataGrid(df, id=TEST_GRID_ID)
|
|
|
|
|
|
@pytest.fixture()
|
|
def grid_id(dg):
|
|
return dg.get_grid_id()
|
|
|
|
|
|
def test_i_can_manage_instance():
|
|
datagrid_1 = DataGrid(id="id1")
|
|
datagrid_2 = DataGrid(id="id2")
|
|
datagrid_3 = DataGrid(id="id1")
|
|
|
|
assert datagrid_1 is not datagrid_2
|
|
assert datagrid_1 is datagrid_3
|
|
|
|
|
|
def test_i_can_render_empty_data_grid():
|
|
grid_id = "testing_grid"
|
|
dg = DataGrid(id=grid_id)
|
|
expected = Div(
|
|
Div(), # container for keyboard handlers
|
|
Div(
|
|
Div(id=f"tt_datagrid-{grid_id}"), # tooltip
|
|
Div(id=f"fa_datagrid-{grid_id}"), # filter component
|
|
Div(name="dt-controls"), # reset filter, resize, ...
|
|
cls="flex justify-between"),
|
|
Div(
|
|
Div(id=f"tsm_datagrid-{grid_id}"), # selection management
|
|
Div(id=f"tdd_datagrid-{grid_id}"), # drag and drop indicator
|
|
Div(id=f"tcdd_datagrid-{grid_id}"), # cell drop down
|
|
Div(), # navigation keyboards
|
|
Div(
|
|
Div(id=f"th_datagrid-{grid_id}"),
|
|
Div(id=f"tb_datagrid-{grid_id}"),
|
|
Div(id=f"tf_datagrid-{grid_id}"),
|
|
),
|
|
Script(),
|
|
id=f"t_datagrid-{grid_id}",
|
|
cls="dt-table"
|
|
)
|
|
)
|
|
|
|
res = dg.__ft__()
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_test_there_is_no_filter_input_when_gs_filter_input_is_false():
|
|
grid_id = "testing_grid"
|
|
grid_setting = {DG_FILTER_INPUT: False}
|
|
dg = DataGrid(id=grid_id, grid_settings=grid_setting)
|
|
|
|
expected = Div(
|
|
Div(), # container for keyboard handlers
|
|
Div(
|
|
Div(id=f"tt_datagrid-{grid_id}"), # tooltip
|
|
Div(Empty), # filter component
|
|
Div(name="dt-controls"), # reset filter, resize, ...
|
|
cls="flex justify-between"),
|
|
Div(
|
|
Div(id=f"tsm_datagrid-{grid_id}"), # selection management
|
|
Div(id=f"tdd_datagrid-{grid_id}"), # drag and drop indicator
|
|
Div(id=f"tcdd_datagrid-{grid_id}"), # cell drop down
|
|
Div(), # navigation keyboards
|
|
Div(
|
|
Div(id=f"th_datagrid-{grid_id}"),
|
|
Div(id=f"tb_datagrid-{grid_id}"),
|
|
Div(id=f"tf_datagrid-{grid_id}")),
|
|
Script(),
|
|
id=f"t_datagrid-{grid_id}",
|
|
cls="dt-table"
|
|
)
|
|
)
|
|
|
|
res = dg.__ft__()
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_there_is_no_filter_button_in_header_when_gs_filter_input_is_false():
|
|
grid_id = "testing_grid"
|
|
grid_setting = {DG_FILTER_INPUT: False}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id=grid_id, grid_settings=grid_setting)
|
|
header = dg.mk_table_header()
|
|
|
|
expected = Div(
|
|
Div(
|
|
Div(Div("Name")), # name
|
|
Div(Div()), # sort
|
|
Div(), # column clickable extension
|
|
Div(Empty), # No filtering icon
|
|
Div(Empty), # No filtering icon
|
|
Div(), # resize handle
|
|
cls='dt-cell dt-draggable dt-resizable', data_col='name'),
|
|
Div(
|
|
Div(Div("Age")), # name
|
|
Div(Div()), # sort
|
|
Div(), # column clickable extension
|
|
Div(Empty), # No filtering icon
|
|
Div(Empty), # No filtering icon
|
|
Div(), # resize handle
|
|
cls='dt-cell dt-draggable dt-resizable', data_col='age'),
|
|
)
|
|
|
|
assert matches(header, expected)
|
|
|
|
|
|
def test_i_can_generate_grid_html(dg, grid_id):
|
|
resize_attr = {"hx-on::after-settle": f"setColumnsWidths('{grid_id}', false);"}
|
|
|
|
expected = Div(
|
|
Div(
|
|
Div(
|
|
Div(Div("Name")),
|
|
Div(Div(), hx_post=f"/{DATAGRID_PATH}/sort"), # sort
|
|
Div(cls="dt-flex-grow"), # column clickable extension
|
|
Div(cls='icon-24 dt-filter-icon icon-btn'),
|
|
Div(cls="dt-filter-popup"),
|
|
Div(cls="dt-resize-handle"),
|
|
data_col="name"
|
|
),
|
|
Div(
|
|
Div(Div("Age")),
|
|
Div(Div(), hx_post=f"/{DATAGRID_PATH}/sort"), # sort
|
|
Div(cls="dt-flex-grow"), # column clickable extension
|
|
Div(cls='icon-24 dt-filter-icon icon-btn'),
|
|
Div(cls="dt-filter-popup"),
|
|
Div(cls="dt-resize-handle"),
|
|
data_col="age"
|
|
),
|
|
cls="dt-row dt-header",
|
|
id=f"th_{grid_id}"),
|
|
Div(
|
|
Div(
|
|
Div(Div("Alice"), data_col="name"),
|
|
Div(Div("20"), data_col="age"),
|
|
),
|
|
Div(
|
|
Div(Div("Bob"), data_col="name"),
|
|
Div(Div("25"), data_col="age"),
|
|
),
|
|
cls="dt-body",
|
|
id=f"tb_{grid_id}",
|
|
**resize_attr,
|
|
),
|
|
Div(
|
|
Div(
|
|
Div(Div(), data_col="name"),
|
|
Div(Div(), data_col="age"),
|
|
),
|
|
id=f"tf_{grid_id}")
|
|
)
|
|
|
|
res = dg.mk_table()
|
|
table = search_elements_by_name(res, None, {"class": "dt-inner-table"})[0]
|
|
# as_ft = debug_print(res)
|
|
matches(table, expected)
|
|
|
|
|
|
def test_i_can_render_boolean():
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25],
|
|
'Is_Good': [True, False]
|
|
})
|
|
dg = DataGrid(df)
|
|
_grid_id = dg.get_grid_id()
|
|
expected = Div(
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20")),
|
|
Div(Div(NotStr('<svg name="CheckboxChecked16Regular"'))),
|
|
),
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25")),
|
|
Div(Div(NotStr('<svg name="CheckboxUnchecked16Regular"'))),
|
|
),
|
|
cls="dt-body",
|
|
id=f"tb_{_grid_id}")
|
|
res = dg.mk_table_body()
|
|
matches(res, expected)
|
|
|
|
|
|
def test_i_can_render_when_width_is_set():
|
|
grid_id = "testing_grid"
|
|
grid_setting = {DG_COLUMNS: {
|
|
"age": {
|
|
"index": 0,
|
|
"title": "Age",
|
|
"type": DG_DATATYPE_NUMBER,
|
|
"width": "150px"
|
|
},
|
|
"name": {
|
|
"index": 1,
|
|
"title": "Name",
|
|
"type": DG_DATATYPE_STRING,
|
|
}
|
|
}}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id=grid_id, grid_settings=grid_setting)
|
|
header = dg.mk_table_header()
|
|
expected = Div(
|
|
Div(
|
|
Div(Div("Age")),
|
|
cls='dt-cell dt-draggable dt-resizable dt-fixed-col', data_col='age', style="width:150px;"),
|
|
Div(
|
|
Div(Div("Name")),
|
|
cls='dt-cell dt-draggable dt-resizable', data_col='name')
|
|
)
|
|
assert matches(header, expected)
|
|
|
|
|
|
def test_i_can_compute_choices_values():
|
|
grid_settings = {DG_COLUMNS: {
|
|
"name": {
|
|
"index": 0,
|
|
"title": "Name",
|
|
},
|
|
"choice1": {
|
|
"index": 3, # Safe index should be used instead of this value
|
|
"title": "Choice1",
|
|
"type": DG_DATATYPE_CHOICE,
|
|
"values": ["A", "B", "C"],
|
|
},
|
|
"choice2": {
|
|
"index": 4, # Safe index should be used instead of this value
|
|
"title": "Choice2",
|
|
"type": DG_DATATYPE_CHOICE,
|
|
"values": ["Alpha", "Beta", "Gamma"],
|
|
}
|
|
}}
|
|
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob', 'Charlie', 'Dave'],
|
|
'Choice1': ['A', 'B', 'C', None],
|
|
'Choice2': ['Gamma', 'Alpha', None, 'Beta'],
|
|
})
|
|
|
|
dg = DataGrid(df, grid_settings=grid_settings)
|
|
assert dg.get_state().choice_columns_values == {
|
|
1: {0: [True, False, False], 1: [False, True, False], 2: [False, False, True], 3: [False, False, False]},
|
|
2: {0: [False, False, True], 1: [True, False, False], 2: [False, False, False], 3: [False, True, False]},
|
|
}
|
|
|
|
#
|
|
# def test_i_can_render_choice():
|
|
# grid_settings = {DG_COLUMNS: {
|
|
# "name": {
|
|
# "index": 0,
|
|
# "title": "Name",
|
|
# },
|
|
# "choice": {
|
|
# "index": 1,
|
|
# "title": "Choice",
|
|
# "type": DG_DATATYPE_CHOICE,
|
|
# "values": ["A", "B", "C"],
|
|
# }
|
|
# }}
|
|
#
|
|
# df = pd.DataFrame({
|
|
# 'Name': ['Alice', 'Bob', 'Charlie'],
|
|
# })
|
|
#
|
|
# dg = DataGrid(df, grid_settings=grid_settings)
|
|
#
|
|
# _grid_id = dg.get_grid_id()
|
|
# expected_header = Div(
|
|
# Div(
|
|
# Div(Div("Name")),
|
|
# Div(Div(), hx_post=f"/{DATAGRID_PATH}/sort"), # sort
|
|
# Div(cls="dt-flex-grow"), # column clickable extension
|
|
# Div(cls='icon-24 dt-filter-icon icon-btn'),
|
|
# Div(cls="dt-filter-popup"),
|
|
# Div(cls="dt-resize-handle"),
|
|
# data_col="name"
|
|
# ),
|
|
# Div(
|
|
# Div(Div("Choice")),
|
|
# Div(Div(), hx_post=f"/{DATAGRID_PATH}/sort"), # sort
|
|
# Div(cls="dt-flex-grow"), # column clickable extension
|
|
# Div(cls='icon-24 dt-filter-icon icon-btn'),
|
|
# Div(cls="dt-filter-popup"),
|
|
# Div(cls="dt-resize-handle"),
|
|
# data_col="choice"
|
|
# ),
|
|
# cls="dt-row dt-header",
|
|
# id=f"th_{_grid_id}")
|
|
#
|
|
# res_header = dg.mk_table_header()
|
|
# matches(res_header, expected_header)
|
|
|
|
# def test_i_can_update_choice_value_column():
|
|
# assert False
|
|
|
|
def test_there_is_no_header_when_gs_header_is_false():
|
|
grid_id = "testing_grid"
|
|
grid_setting = {DG_TABLE_HEADER: False}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id=grid_id, grid_settings=grid_setting)
|
|
header = dg.mk_table_header()
|
|
|
|
assert matches(header, Div(cls="dt-row dt-header hidden"))
|
|
|
|
|
|
def test_there_is_no_footer_when_gs_footer_is_false():
|
|
grid_id = "testing_grid"
|
|
grid_setting = {DG_TABLE_FOOTER: False}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id=grid_id, grid_settings=grid_setting)
|
|
footer = dg.mk_table_footer()
|
|
|
|
assert matches(footer, Div(Empty))
|
|
|
|
|
|
def test_i_can_render_when_some_columns_are_not_visible(dg, grid_id):
|
|
grid_id = "testing_grid"
|
|
grid_setting = {DG_COLUMNS: {
|
|
"name": {VISIBLE_KEY: False, INDEX_KEY: 0, "agg_func": DG_AGGREGATE_SUM},
|
|
"age": {INDEX_KEY: 1, "agg_func": DG_AGGREGATE_SUM},
|
|
}}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, id=grid_id, grid_settings=grid_setting)
|
|
table_header = dg.mk_table_header()
|
|
table_body = dg.mk_table_body()
|
|
table_footer = dg.mk_table_footer()
|
|
|
|
expected_header = Div(
|
|
Div(cls="dt-header-hidden", hx_post=f"/{DATAGRID_PATH}/on_state_changed", ),
|
|
Div(data_col="age", data_tooltip="Age"),
|
|
)
|
|
assert matches(table_header, expected_header)
|
|
|
|
expected_body = Div(
|
|
Div(Div(cls="dt-col-hidden"), Div(data_col="age", data_tooltip="20"), ), # first row
|
|
Div(Div(cls="dt-col-hidden"), Div(data_col="age", data_tooltip="25"), ), # second row
|
|
)
|
|
assert matches(table_body, expected_body)
|
|
|
|
expected_footer = Div(
|
|
Div(Div(cls="dt-col-hidden"), Div(data_col="age"))
|
|
)
|
|
assert matches(table_footer, expected_footer)
|
|
|
|
|
|
def test_i_can_hide_a_column(dg, grid_id):
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name"], "true", "false")
|
|
table_header = dg.mk_table_header()
|
|
table_body = dg.mk_table_body()
|
|
table_footer = dg.mk_table_footer()
|
|
|
|
expected_header = Div(
|
|
Div(cls="dt-header-hidden", hx_post=f"/{DATAGRID_PATH}/on_state_changed", ),
|
|
Div(data_col="age", data_tooltip="Age"),
|
|
)
|
|
assert matches(table_header, expected_header)
|
|
|
|
expected_body = Div(
|
|
Div(Div(cls="dt-col-hidden"), Div(data_col="age", data_tooltip="20"), ), # first row
|
|
Div(Div(cls="dt-col-hidden"), Div(data_col="age", data_tooltip="25"), ), # second row
|
|
)
|
|
assert matches(table_body, expected_body)
|
|
|
|
expected_footer = Div(
|
|
Div(Div(cls="dt-col-hidden"), Div(data_col="age"))
|
|
)
|
|
assert matches(table_footer, expected_footer)
|
|
|
|
|
|
def test_i_can_generate_sorting_capabilities(dg, grid_id):
|
|
name_attrs = {
|
|
"hx_post": f"/{DATAGRID_PATH}/sort",
|
|
"hx_vals": f'{{"g_id": "{grid_id}", "c_id":"name"}}',
|
|
"hx_target": f"#tb_{grid_id}",
|
|
}
|
|
age_attrs = {
|
|
"hx_post": f"/{DATAGRID_PATH}/sort",
|
|
"hx_vals": f'{{"g_id": "{grid_id}", "c_id":"age"}}',
|
|
"hx_target": f"#tb_{grid_id}",
|
|
}
|
|
expected = Div(
|
|
Div(
|
|
Div(Div("Name")),
|
|
Div(
|
|
Div(id=f"tsi_datagrid-testing_grid_id_name"),
|
|
**name_attrs)),
|
|
Div(
|
|
Div(Div("Age")),
|
|
Div(
|
|
Div(id=f"tsi_datagrid-testing_grid_id_age"),
|
|
**age_attrs))
|
|
)
|
|
|
|
res = dg.mk_table_header()
|
|
# as_ft = debug_print(res[0])
|
|
matches(res, expected)
|
|
|
|
|
|
def test_i_can_sort(dg):
|
|
state = dg.get_state()
|
|
assert state.sorted == []
|
|
assert dg.get_sort_icons() == {}
|
|
|
|
dg.sort("name") # asc
|
|
assert state.sorted == [SortDefinition(column_id='name', direction=1)]
|
|
assert "name" in dg.get_sort_icons()
|
|
assert matches(dg.mk_table_body(), Div(
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20")),
|
|
),
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25")),
|
|
),
|
|
))
|
|
|
|
dg.sort("name") # desc
|
|
assert state.sorted == [SortDefinition(column_id='name', direction=2)]
|
|
assert "name" in dg.get_sort_icons()
|
|
assert matches(dg.mk_table_body(), Div(
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25")),
|
|
),
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20")),
|
|
),
|
|
))
|
|
|
|
dg.sort("name") # back to default
|
|
assert state.sorted == [SortDefinition(column_id='name', direction=0)]
|
|
assert "name" not in dg.get_sort_icons()
|
|
assert matches(dg.mk_table_body(), Div(
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20")),
|
|
),
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25")),
|
|
),
|
|
))
|
|
|
|
dg.sort("name") # asc again
|
|
assert state.sorted == [SortDefinition(column_id='name', direction=1)]
|
|
assert "name" in dg.get_sort_icons()
|
|
assert matches(dg.mk_table_body(), Div(
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20")),
|
|
),
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25")),
|
|
),
|
|
))
|
|
|
|
|
|
def test_i_can_filter_when_found(dg, grid_id):
|
|
dg.filter(FILTER_INPUT_CID, 'Ali') # filter is not case-sensitive !
|
|
expected = Div(
|
|
Div(
|
|
Div(
|
|
Div(Span('Ali', cls='dt-highlight-1'),
|
|
Span('ce'))
|
|
),
|
|
Div(Div("20"))
|
|
),
|
|
id=f"tb_{grid_id}"
|
|
)
|
|
|
|
res = dg.mk_table_body()
|
|
matches(res, expected)
|
|
|
|
table_content = extract_table_values(res, header=False)
|
|
assert len(table_content) == 1
|
|
|
|
|
|
def test_i_can_filter_when_not_found(dg, grid_id):
|
|
dg.filter(FILTER_INPUT_CID, 'not found')
|
|
expected = Div(id=f"tb_{grid_id}") # empty grid
|
|
|
|
res = dg.mk_table_body()
|
|
matches(res, expected)
|
|
|
|
|
|
def test_i_can_filter_a_column(dg):
|
|
dg.filter('name', ['Alice'])
|
|
expected = Div(
|
|
Div(
|
|
Div(Div('Alice')),
|
|
Div(Div("20"))
|
|
),
|
|
)
|
|
res = dg.mk_table_body()
|
|
matches(res, expected)
|
|
|
|
table_content = extract_table_values(res, header=False)
|
|
assert len(table_content) == 1
|
|
|
|
|
|
def test_i_can_filter_an_int_column(dg):
|
|
dg.filter('age', ['20']) # not that we filter as string
|
|
expected = Div(
|
|
Div(
|
|
Div(Div('Alice')),
|
|
Div(Div("20"))
|
|
),
|
|
)
|
|
res = dg.mk_table_body()
|
|
matches(res, expected)
|
|
|
|
table_content = extract_table_values(res, header=False)
|
|
assert len(table_content) == 1
|
|
|
|
|
|
def test_i_can_reset_filter(dg):
|
|
expected = Div(
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20")),
|
|
),
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25")),
|
|
),
|
|
)
|
|
|
|
dg.filter(FILTER_INPUT_CID, 'ali')
|
|
dg.reset_filter(None)
|
|
res = dg.mk_table_body()
|
|
matches(res, expected)
|
|
|
|
|
|
def test_compute_grid_settings():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
|
|
settings = DataGrid.compute_grid_settings(df)
|
|
assert settings[DG_COLUMNS] == {'age': {'index': 1, 'safe_index': 1, 'title': 'Age', 'type': 'number'},
|
|
'name': {'index': 0, 'safe_index': 0, 'title': 'Name', 'type': 'string'}}
|
|
|
|
settings = DataGrid.compute_grid_settings(df, grid_setting={})
|
|
assert settings[DG_COLUMNS] == {'age': {'index': 1, 'safe_index': 1, 'title': 'Age', 'type': 'number'},
|
|
'name': {'index': 0, 'safe_index': 0, 'title': 'Name', 'type': 'string'}}
|
|
|
|
settings = DataGrid.compute_grid_settings(df, grid_setting={DG_COLUMNS: {}})
|
|
assert settings[DG_COLUMNS] == {}
|
|
|
|
|
|
def test_i_can_compute_safe_index():
|
|
grid_settings = {DG_COLUMNS: {'age': {'index': 15},
|
|
'name': {'index': 10, },
|
|
}}
|
|
|
|
settings = DataGrid.compute_grid_settings(None, grid_setting=grid_settings)
|
|
assert settings[DG_COLUMNS] == {'age': {'index': 15, 'safe_index': 1, 'title': 'Age'},
|
|
'name': {'index': 10, 'safe_index': 0, 'title': 'Name'}, }
|
|
|
|
|
|
def test_i_can_filter_the_columns_using_compute_grid_settings():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
settings = DataGrid.compute_grid_settings(df, columns=["age"])
|
|
|
|
assert settings[DG_COLUMNS] == {'age': {'safe_index': 0, 'title': 'Age'}}
|
|
|
|
|
|
def test_i_can_add_new_columns_when_compute_grid_settings():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
settings = DataGrid.compute_grid_settings(df, columns=["new_column", "age"])
|
|
|
|
assert settings[DG_COLUMNS] == {'new_column': {'safe_index': 0, 'title': 'New Column'},
|
|
'age': {'safe_index': 1, 'title': 'Age'}}
|
|
|
|
|
|
def test_i_can_add_new_columns_when_compute_grid_settings_from_nothing():
|
|
settings = DataGrid.compute_grid_settings(None, columns=["new_column", "age"])
|
|
|
|
assert settings[DG_COLUMNS] == {'new_column': {'safe_index': 0, 'title': 'New Column'},
|
|
'age': {'safe_index': 1, 'title': 'Age'}}
|
|
|
|
|
|
def test_i_can_rearrange_columns_using_compute_grid_settings():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
settings = DataGrid.compute_grid_settings(df, columns=["age", "other_column_1", "other_column_2"])
|
|
|
|
assert settings[DG_COLUMNS] == {
|
|
'age': {'safe_index': 0, 'title': 'Age'},
|
|
'other_column_1': {'safe_index': 1, 'title': 'Other Column 1'},
|
|
'other_column_2': {'safe_index': 2, 'title': 'Other Column 2'}}
|
|
|
|
|
|
def test_i_can_init_with_simplified_grid_settings():
|
|
grid_settings = {DG_COLUMNS: ["Age", "Other Column 1", "Other Column 2"]}
|
|
dg = DataGrid(grid_settings=grid_settings)
|
|
settings = dg._grid_settings
|
|
assert settings[DG_COLUMNS] == {'age': {'index': 0, 'safe_index': 0, 'title': 'Age'},
|
|
'other_column_1': {'index': 1, 'safe_index': 1, 'title': 'Other Column 1'},
|
|
'other_column_2': {'index': 2, 'safe_index': 2, 'title': 'Other Column 2'}}
|
|
|
|
|
|
def test_i_can_update_dataframe():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
|
|
expected = pd.DataFrame({
|
|
'name': ['Bob', 'Alice'],
|
|
'age': [25, 20]
|
|
})
|
|
|
|
grid_settings = DataGrid.compute_grid_settings(df)
|
|
df = DataGrid._get_updated_dataframe(df, grid_settings)
|
|
assert df.equals(expected)
|
|
|
|
|
|
def test_i_can_update_dataframe_when_changing_column_order():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
|
|
expected = pd.DataFrame({
|
|
'age': [25, 20],
|
|
'name': ['Bob', 'Alice'],
|
|
})
|
|
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
'age': {'index': 0, 'title': 'Age'},
|
|
'name': {'index': 1, 'title': 'Name'}
|
|
}
|
|
}
|
|
|
|
df = DataGrid._get_updated_dataframe(df, grid_settings)
|
|
assert df.equals(expected)
|
|
|
|
|
|
def test_i_can_update_dataframe_when_changing_column_order_and_adding_new_columns():
|
|
df = pd.DataFrame({
|
|
'Name': ['Bob', 'Alice'],
|
|
'Age': [25, 20]
|
|
})
|
|
|
|
expected = pd.DataFrame({
|
|
'age': [25, 20],
|
|
'new_column': [None, None],
|
|
'name': ['Bob', 'Alice'],
|
|
})
|
|
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
'age': {'index': 0, 'title': 'Age'},
|
|
'new_column': {'index': 1, 'title': 'New Column'},
|
|
'name': {'index': 2, 'title': 'Name'}
|
|
}
|
|
}
|
|
|
|
df = DataGrid._get_updated_dataframe(df, grid_settings)
|
|
assert df.equals(expected)
|
|
|
|
|
|
def test_i_can_import_file_when_none():
|
|
dg = DataGrid()
|
|
dg.import_file(None, None)
|
|
|
|
assert dg._df is None
|
|
assert dg._grid_settings[DG_COLUMNS] == {}
|
|
|
|
|
|
def test_i_can_reset_df_when_import_none(dg):
|
|
assert dg._df is not None
|
|
assert dg._grid_settings[DG_COLUMNS] != {}
|
|
|
|
dg.import_file(None, None)
|
|
|
|
assert dg._df is None
|
|
assert dg._grid_settings[DG_COLUMNS] == {}
|
|
|
|
|
|
def test_i_can_import_a_file(excel_file_content):
|
|
dg = DataGrid()
|
|
dg.import_file("file_name", excel_file_content)
|
|
|
|
assert dg._df is not None
|
|
assert dg._grid_settings[DG_COLUMNS] == {
|
|
'column_1': {'index': 0, 'safe_index': 0, 'title': 'Column 1', 'type': 'string'},
|
|
'column_2': {'index': 1, 'safe_index': 1, 'title': 'Column 2', 'type': 'string'}}
|
|
|
|
|
|
def test_i_can_reimport_a_file(dg, excel_file_content):
|
|
dg.import_file("file_name", excel_file_content)
|
|
|
|
assert dg._df is not None
|
|
assert dg._grid_settings[DG_COLUMNS] == {
|
|
'column_1': {'index': 0, 'safe_index': 0, 'title': 'Column 1', 'type': 'string'},
|
|
'column_2': {'index': 1, 'safe_index': 1, 'title': 'Column 2', 'type': 'string'}}
|
|
|
|
|
|
def test_grid_setting_is_kept_on_import(dg, excel_file_content):
|
|
grid_settings = {DG_FILTER_INPUT: False}
|
|
dg = DataGrid(grid_settings=grid_settings)
|
|
dg.import_file("file_name", excel_file_content)
|
|
|
|
assert dg._grid_settings[DG_FILTER_INPUT] is False
|
|
|
|
|
|
def test_i_can_get_filter_content(dg2):
|
|
res = dg2.mk_filter_popup_content('name')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'Alice': False,
|
|
'Bob': False,
|
|
'Charlie': False,
|
|
'David': False})
|
|
|
|
dg2.filter('name', ['Bob', 'Charlie'])
|
|
res = dg2.mk_filter_popup_content('name')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'Alice': False,
|
|
'Bob': True,
|
|
'Charlie': True,
|
|
'David': False})
|
|
|
|
|
|
def test_i_can_get_filter_content_when_multiple_filter_set(dg2):
|
|
dg2.filter('name', ['Bob', 'Charlie', 'David'])
|
|
res = dg2.mk_filter_popup_content('age')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'25': False,
|
|
'30': False,
|
|
'35': False,
|
|
})
|
|
|
|
# second filter
|
|
dg2.filter('age', ["25", "30"])
|
|
res = dg2.mk_filter_popup_content('city')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'Los Angeles': False,
|
|
'Chicago': False,
|
|
})
|
|
|
|
# previous filters are remembered
|
|
res = dg2.mk_filter_popup_content('name')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'Alice': False,
|
|
'Bob': True,
|
|
'Charlie': True,
|
|
'David': True,
|
|
})
|
|
|
|
res = dg2.mk_filter_popup_content('age')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'25': True,
|
|
'30': True,
|
|
'35': False,
|
|
})
|
|
|
|
res = dg2.mk_filter_popup_content('city')
|
|
checkboxes = extract_popup_content(res, filter_input=False)
|
|
assert checkboxes == OrderedDict({
|
|
'Los Angeles': False,
|
|
'Chicago': False,
|
|
})
|
|
|
|
|
|
def test_i_can_import_data(dg):
|
|
new_data = pd.DataFrame({
|
|
'City': ['New York', 'Los Angeles'],
|
|
'Population': [8_331_237, 3_965_117]
|
|
})
|
|
dg.import_dataframe(new_data)
|
|
|
|
table = dg.mk_table()
|
|
content = extract_table_values(table)
|
|
|
|
assert content == OrderedDict({'City': ['New York', 'Los Angeles'],
|
|
'Population': ['8331237', '3965117']})
|
|
|
|
assert dg._grid_settings[DG_COLUMNS] == {
|
|
'city': {'index': 0, 'safe_index': 0, 'title': 'City', 'type': 'string'},
|
|
'population': {'index': 1, 'safe_index': 1, 'title': 'Population', 'type': 'number'}
|
|
}
|
|
|
|
|
|
def test_i_can_import_data_when_none():
|
|
dg = DataGrid()
|
|
dg.import_dataframe(None)
|
|
|
|
assert dg._df is None
|
|
assert dg._grid_settings[DG_COLUMNS] == {}
|
|
|
|
|
|
def test_i_can_import_data_when_empty():
|
|
dg = DataGrid()
|
|
dg.import_dataframe(pd.DataFrame())
|
|
assert dg._df.empty
|
|
assert dg._grid_settings[DG_COLUMNS] == {}
|
|
|
|
|
|
def test_grid_setting_is_kept_when_no_data():
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
"name": {
|
|
"index": 0,
|
|
"title": "Name",
|
|
"type": "string",
|
|
},
|
|
}
|
|
}
|
|
dg = DataGrid(grid_settings=grid_settings) # df is None
|
|
assert dg._grid_settings[DG_COLUMNS] == grid_settings[DG_COLUMNS]
|
|
|
|
|
|
def test_grid_settings_is_kept_after_load_data(dg):
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
"name": {
|
|
"index": 0,
|
|
"title": "Name",
|
|
"type": "string",
|
|
},
|
|
}
|
|
}
|
|
df = pd.DataFrame({"name": ["Alice", "Bob"],
|
|
"age": [20, 25]})
|
|
dg = DataGrid(grid_settings=grid_settings)
|
|
dg.import_dataframe(df, reset=False)
|
|
assert dg._grid_settings[DG_COLUMNS] == {
|
|
"name": {
|
|
"index": 0,
|
|
'safe_index': 0,
|
|
"title": "Name",
|
|
"type": "string",
|
|
}
|
|
}
|
|
|
|
|
|
def test_i_can_apply_style_depending_on_column_type():
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
"name": {
|
|
"index": 5,
|
|
"title": "Name",
|
|
"type": DG_DATATYPE_STRING,
|
|
},
|
|
"age": {
|
|
"index": 10,
|
|
"title": "Age",
|
|
"type": DG_DATATYPE_NUMBER,
|
|
},
|
|
"male": {
|
|
"index": 15,
|
|
"title": "Male",
|
|
"type": DG_DATATYPE_BOOL,
|
|
},
|
|
}
|
|
}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25],
|
|
'male': [True, False],
|
|
})
|
|
|
|
dg = DataGrid(df, grid_settings=grid_settings)
|
|
|
|
expected = Div(
|
|
Div(
|
|
Div(Div("Alice")),
|
|
Div(Div("20"), style="text-align: right;"),
|
|
Div(Div(), style="text-align: center; display: flex; justify-content: center;"),
|
|
),
|
|
Div(
|
|
Div(Div("Bob")),
|
|
Div(Div("25"), style="text-align: right;"),
|
|
Div(Div(), style="text-align: center; display: flex; justify-content: center;"),
|
|
)
|
|
)
|
|
|
|
actual = dg.mk_table_body()
|
|
assert matches(actual, expected)
|
|
|
|
|
|
def test_by_default_table_is_ready_only(dg):
|
|
# by default the grid is readonly
|
|
# The cell will be selected, but no input
|
|
|
|
for cell in [(0, 0), (1, 0), (0, 1), (1, 1)]:
|
|
res = dg.try_enter_edit_mode(*cell)
|
|
assert res is None
|
|
|
|
|
|
def test_i_can_try_edit_with_invalid_values(dg):
|
|
assert dg.try_enter_edit_mode(-1, 0) is None
|
|
assert dg.try_enter_edit_mode(0, 3) is None
|
|
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
assert dg.try_enter_edit_mode(-1, 0) is None
|
|
assert dg.try_enter_edit_mode(0, 3) is None
|
|
|
|
|
|
def test_i_can_try_edit_a_text_cell(dg2, grid_id):
|
|
# make the grid editable
|
|
dg2.get_grid_settings()[DG_READ_ONLY] = False
|
|
res = dg2.try_enter_edit_mode(0, 1)
|
|
|
|
expected = Div(
|
|
Input(name="new_value", value="Bob", cls="dt-cell-content"),
|
|
cls="dt-cell-is-editing"
|
|
)
|
|
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_edit_a_number_cell(dg2, grid_id):
|
|
# make the grid editable
|
|
dg2.get_grid_settings()[DG_READ_ONLY] = False
|
|
res = dg2.try_enter_edit_mode(1, 1)
|
|
|
|
expected = Div(
|
|
Input(name="new_value", value=25, cls="dt-cell-content"),
|
|
cls="dt-cell-is-editing"
|
|
)
|
|
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_edit_a_bool_cell(dg2, grid_id):
|
|
# 'Student': [True, False, True, False],
|
|
# make the grid editable
|
|
dg2.get_grid_settings()[DG_READ_ONLY] = False
|
|
res = dg2.try_enter_edit_mode(3, 1)
|
|
|
|
assert res is None # bool cell never enters in edit mode
|
|
|
|
|
|
def test_i_can_manage_click_when_read_only(dg, grid_id):
|
|
# The cell is selected, but no input
|
|
dg.get_grid_settings()[DG_READ_ONLY] = True
|
|
res = dg.manage_cell_click(0, 0)
|
|
|
|
input_elt = search_elements_by_name(res, "input")
|
|
assert input_elt is None
|
|
|
|
under_edition = search_elements_by_name(res, None, {"class": "dt-cell-is-editing"})
|
|
assert under_edition is None
|
|
|
|
|
|
def test_i_can_manage_click_for_text_when_not_read_only(dg, grid_id):
|
|
# the cell is selected and the input element is returned
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
res = dg.manage_cell_click(0, 0)
|
|
|
|
under_edition = search_elements_by_name(res, None, {"class": "dt-cell-is-editing"})
|
|
assert under_edition is not None
|
|
|
|
input_elt = search_elements_by_name(res, "input")
|
|
assert input_elt is not None
|
|
|
|
|
|
def test_i_can_manage_click_for_number_when_not_read_only(dg, grid_id):
|
|
# the cell is selected and the input element is returned
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
res = dg.manage_cell_click(1, 0)
|
|
|
|
under_edition = search_elements_by_name(res, None, {"class": "dt-cell-is-editing"})
|
|
assert under_edition is not None
|
|
|
|
input_elt = search_elements_by_name(res, "input")
|
|
assert input_elt is not None
|
|
|
|
|
|
def test_i_can_manage_click_for_bool_when_not_read_only(dg2, grid_id):
|
|
# the value is changed, the cell is selected, no input is returned
|
|
dg2.get_grid_settings()[DG_READ_ONLY] = False
|
|
old_value = dg2.get_table_value(3, 0)
|
|
res = dg2.manage_cell_click(3, 0)
|
|
|
|
under_edition = search_elements_by_name(res, None, {"class": "dt-cell-is-editing"})
|
|
assert under_edition is None
|
|
|
|
input_elt = search_elements_by_name(res, "input")
|
|
assert input_elt is None
|
|
|
|
assert dg2.get_table_value(3, 0) == (not old_value)
|
|
|
|
|
|
def test_i_can_manage_multiple_click_when_not_read_only(dg, grid_id):
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
dg.manage_cell_click(0, 0)
|
|
|
|
res = dg.manage_cell_click(1, 0)
|
|
expected = [Div(Div("Alice"), hx_swap_oob="innerHTML", id=f"tc_{grid_id}-0-0"), # reset the previous edition
|
|
Div(Div(cls="dt-cell-is-editing"), id=f"tc_{grid_id}-1-0"), # new input
|
|
Div(id=f"tsm_{grid_id}", )]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_edit_one_when_the_column_is_editable():
|
|
grid_settings = {DG_COLUMNS: {
|
|
"name": {
|
|
"index": 0,
|
|
"title": "Name",
|
|
"type": "string",
|
|
},
|
|
"age": {
|
|
"index": 1,
|
|
"title": "Age",
|
|
"type": "number",
|
|
DG_READ_ONLY: False,
|
|
}}}
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
dg = DataGrid(df, grid_settings=grid_settings)
|
|
|
|
# I cannot modify a column that is readonly
|
|
res = dg.try_enter_edit_mode(0, 0)
|
|
assert res is None
|
|
|
|
# I can modify when it's not readonly
|
|
res = dg.try_enter_edit_mode(1, 0)
|
|
expected = Div(
|
|
Input(name="new_value", value=20, cls="dt-cell-content"),
|
|
cls="dt-cell-is-editing"
|
|
)
|
|
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_cannot_edit_bool_when_the_column_is_not_editable(dg2, grid_id):
|
|
dg2.get_grid_settings()[DG_READ_ONLY] = True
|
|
old_value = dg2.get_table_value(3, 0)
|
|
dg2.manage_cell_click(3, 0)
|
|
|
|
assert dg2.get_table_value(3, 0) == old_value
|
|
|
|
|
|
@pytest.mark.parametrize("mode, expected", [
|
|
(None, DG_SELECTION_MODE_CELL),
|
|
(DG_SELECTION_MODE_CELL, DG_SELECTION_MODE_CELL),
|
|
(DG_SELECTION_MODE_COLUMN, DG_SELECTION_MODE_COLUMN),
|
|
(DG_SELECTION_MODE_ROW, DG_SELECTION_MODE_ROW),
|
|
])
|
|
def test_i_can_set_selection_mode(mode, expected):
|
|
grid_settings = {
|
|
DG_SELECTION_MODE: mode
|
|
}
|
|
dg = DataGrid(grid_settings=grid_settings)
|
|
assert dg.get_state().selection_mode == expected
|
|
|
|
|
|
@pytest.mark.parametrize("read_only", [True, False])
|
|
def test_i_can_manage_cell_selection_when_selection_mode_is_cell(dg, grid_id, read_only):
|
|
# The cell is selected, but no input
|
|
dg.get_state().selection_mode = DG_SELECTION_MODE_CELL
|
|
dg.get_grid_settings()[DG_READ_ONLY] = read_only
|
|
res = dg.manage_cell_click(0, 0)
|
|
|
|
assert get_selected(res) == [
|
|
("cell", 0, 0),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("read_only", [True, False])
|
|
def test_i_can_manage_cell_selection_when_selection_mode_is_row(dg, grid_id, read_only):
|
|
# The cell is selected, but no input
|
|
dg.get_grid_settings()[DG_READ_ONLY] = read_only
|
|
dg.get_state().selection_mode = DG_SELECTION_MODE_ROW
|
|
res = dg.manage_cell_click(0, 0)
|
|
|
|
assert get_selected(res) == [
|
|
("row", 0),
|
|
("cell", 0, 0),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("read_only", [True, False])
|
|
def test_i_can_manage_cell_selection_when_selection_mode_is_column(dg, grid_id, read_only):
|
|
# The cell is selected, but no input
|
|
dg.get_grid_settings()[DG_READ_ONLY] = read_only
|
|
dg.get_state().selection_mode = DG_SELECTION_MODE_COLUMN
|
|
res = dg.manage_cell_click(0, 0)
|
|
|
|
assert get_selected(res) == [
|
|
("column", "name"),
|
|
("cell", 0, 0),
|
|
]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_using_control_key(dg, grid_id):
|
|
dg.manage_cell_click(0, 0)
|
|
res = dg.manage_cell_click(1, 0, "ctrl-")
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [("cell", 1, 0), ('cellx', 0, 0), ('cellx', 1, 0)]
|
|
|
|
assert dg.get_state().extra_selected == [('cell', 'tc_datagrid-testing_grid_id-0-0'),
|
|
('cell', 'tc_datagrid-testing_grid_id-1-0')]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_in_colum_mode_using_control_key(dg, grid_id):
|
|
dg.get_state().selection_mode = DG_SELECTION_MODE_COLUMN
|
|
dg.manage_cell_click(0, 0)
|
|
res = dg.manage_cell_click(1, 0, "ctrl-")
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('column', "age"), ('cell', 1, 0), ('column', "name")]
|
|
|
|
assert dg.get_state().extra_selected == [('column', "name"), ('column', "age")]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_in_row_mode_using_control_key(dg, grid_id):
|
|
dg.get_state().selection_mode = DG_SELECTION_MODE_ROW
|
|
dg.manage_cell_click(0, 0)
|
|
res = dg.manage_cell_click(0, 1, "ctrl-")
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 1), ('cell', 0, 1), ('row', 0)]
|
|
|
|
assert dg.get_state().extra_selected == [('row', 'tr_datagrid-testing_grid_id-0'),
|
|
('row', 'tr_datagrid-testing_grid_id-1')]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_using_shift_key(dg2, grid_id):
|
|
dg2.manage_cell_click(1, 0)
|
|
res = dg2.manage_cell_click(2, 2, "shift-")
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('cell', 2, 2),
|
|
('cellx', 1, 0),
|
|
('cellx', 1, 1),
|
|
('cellx', 1, 2),
|
|
('cellx', 2, 0),
|
|
('cellx', 2, 1),
|
|
('cellx', 2, 2)]
|
|
|
|
assert dg2.get_state().extra_selected == [('cell', 'tc_datagrid-testing_grid_id-1-0'),
|
|
('cell', 'tc_datagrid-testing_grid_id-1-1'),
|
|
('cell', 'tc_datagrid-testing_grid_id-1-2'),
|
|
('cell', 'tc_datagrid-testing_grid_id-2-0'),
|
|
('cell', 'tc_datagrid-testing_grid_id-2-1'),
|
|
('cell', 'tc_datagrid-testing_grid_id-2-2')]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_in_colum_mode_using_shift_key(dg2, grid_id):
|
|
dg2.get_state().selection_mode = DG_SELECTION_MODE_COLUMN
|
|
dg2.manage_cell_click(1, 0)
|
|
res = dg2.manage_cell_click(3, 3, "shift-")
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('column', 'student'), ('cell', 3, 3), ('column', "age"), ('column', "city")]
|
|
|
|
assert dg2.get_state().extra_selected == [('column', "age"), ('column', "city"), ('column', "student")]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_in_row_mode_using_shift_key(dg2, grid_id):
|
|
dg2.get_state().selection_mode = DG_SELECTION_MODE_ROW
|
|
dg2.manage_cell_click(1, 0)
|
|
res = dg2.manage_cell_click(3, 3, "shift-")
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 3), ('cell', 3, 3), ('row', 0), ('row', 1), ('row', 2)]
|
|
|
|
assert dg2.get_state().extra_selected == [('row', 'tr_datagrid-testing_grid_id-0'),
|
|
('row', 'tr_datagrid-testing_grid_id-1'),
|
|
('row', 'tr_datagrid-testing_grid_id-2'),
|
|
('row', 'tr_datagrid-testing_grid_id-3')]
|
|
|
|
|
|
def test_i_can_manage_multiple_cells_selection_using_shift_key_when_the_table_is_sorted(dg2, grid_id):
|
|
dg2.sort("city")
|
|
# name age city student date
|
|
# 2 Charlie 30 Chicago True 2007-01-01
|
|
# 3 David 35 Houston False 2000-12-31
|
|
# 1 Bob 25 Los Angeles False 2022-10-01
|
|
# 0 Alice 20 New York True 2020-01-10
|
|
|
|
# I click on age=35 (pos=1,1) then on city='Los Angeles' (pos=2,2)
|
|
dg2.manage_cell_click(1, 3) # age=35 position is 1,1
|
|
res = dg2.manage_cell_click(2, 1, "shift-") # city=los angeles is position 2,2
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('cell', 2, 1),
|
|
('cellx', 1, 3),
|
|
('cellx', 1, 1),
|
|
('cellx', 2, 3),
|
|
('cellx', 2, 1)]
|
|
|
|
assert dg2.get_state().extra_selected == [('cell', 'tc_datagrid-testing_grid_id-1-3'),
|
|
('cell', 'tc_datagrid-testing_grid_id-1-1'),
|
|
('cell', 'tc_datagrid-testing_grid_id-2-3'),
|
|
('cell', 'tc_datagrid-testing_grid_id-2-1')]
|
|
|
|
|
|
def test_i_can_select_column(dg, grid_id):
|
|
res = dg.manage_column_click(1, '')
|
|
assert get_selected(res) == [
|
|
("column", "age"),
|
|
]
|
|
|
|
# without any modifier, the selection is replaced
|
|
res = dg.manage_column_click(0, '')
|
|
assert get_selected(res) == [
|
|
("column", "name"),
|
|
]
|
|
|
|
|
|
def test_i_can_select_multiple_columns_with_control_key(dg, grid_id):
|
|
dg.manage_column_click(1, '')
|
|
|
|
# I can add column using ctrl
|
|
res = dg.manage_column_click(0, "ctrl-")
|
|
assert get_selected(res) == [
|
|
("column", "age"),
|
|
("column", "name"),
|
|
]
|
|
|
|
# I can remove column using control
|
|
res = dg.manage_column_click(1, "ctrl-")
|
|
assert get_selected(res) == [
|
|
("column", "name"),
|
|
]
|
|
|
|
|
|
def test_i_can_select_multiple_columns_with_shift_key(dg2, grid_id):
|
|
res = dg2.manage_column_click(1, "shift-")
|
|
assert get_selected(res) == [
|
|
("column", "age"),
|
|
]
|
|
|
|
res = dg2.manage_column_click(3, "shift-")
|
|
assert get_selected(res) == [
|
|
("column", "age"),
|
|
("column", "city"),
|
|
("column", "student"),
|
|
]
|
|
|
|
res = dg2.manage_column_click(2, "shift-")
|
|
assert get_selected(res) == [
|
|
("column", "age"),
|
|
("column", "city"),
|
|
]
|
|
|
|
res = dg2.manage_column_click(0, "shift-")
|
|
assert get_selected(res) == [
|
|
("column", "name"),
|
|
("column", "age"),
|
|
]
|
|
|
|
|
|
def test_i_can_press_enter_when_readonly(dg, grid_id):
|
|
dg.get_grid_settings()[DG_READ_ONLY] = True
|
|
dg.manage_cell_click(0, 0) # select the top left
|
|
|
|
res = dg.manage_key_pressed("Enter", None)
|
|
expected = [Div(Div(selection_type="cell", element_id=f"tc_{grid_id}-0-1"),
|
|
id=f"tsm_{grid_id}")]
|
|
assert matches(res, expected)
|
|
|
|
res = dg.manage_key_pressed("Enter", None)
|
|
expected = [Div(Div(selection_type="cell", element_id=f"tc_{grid_id}-0-0"),
|
|
id=f"tsm_{grid_id}", )]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_press_tab_when_readonly(dg, grid_id):
|
|
dg.get_grid_settings()[DG_READ_ONLY] = True
|
|
dg.manage_cell_click(0, 0) # select the top left
|
|
|
|
res = dg.manage_key_pressed("Tab", None)
|
|
expected = [Div(Div(selection_type="cell", element_id=f"tc_{grid_id}-1-0"),
|
|
id=f"tsm_{grid_id}")]
|
|
assert matches(res, expected)
|
|
|
|
res = dg.manage_key_pressed("Tab", None)
|
|
expected = [Div(Div(selection_type="cell", element_id=f"tc_{grid_id}-0-1"),
|
|
id=f"tsm_{grid_id}")]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_press_enter_when_not_readonly(dg, grid_id):
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
dg.manage_cell_click(0, 0) # select the top left
|
|
|
|
res = dg.manage_key_pressed("Enter", "Alice")
|
|
expected = [
|
|
Div(
|
|
Div("Alice"),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-0-0"),
|
|
Div(
|
|
Div(Input()),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-0-1"),
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-0-1"),
|
|
id=f"tsm_{grid_id}",
|
|
)]
|
|
assert matches(res, expected)
|
|
|
|
res = dg.manage_key_pressed("Enter", "Bob")
|
|
expected = [
|
|
Div(
|
|
Div("Bob"),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-0-1"),
|
|
Div(
|
|
Div(Input()),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-0-0"),
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-0-0"),
|
|
id=f"tsm_{grid_id}",
|
|
)]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_press_enter_on_a_bool_editable_cell(dg2, grid_id):
|
|
dg2.get_grid_settings()[DG_READ_ONLY] = False
|
|
dg2.get_state().selected = (3, 0)
|
|
|
|
res = dg2.manage_key_pressed("Enter", None)
|
|
expected = [
|
|
Div(
|
|
Div(
|
|
NotStr('<svg name="CheckboxUnchecked'),
|
|
cls="icon-bool",
|
|
),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-3-0"),
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-3-1"),
|
|
id=f"tsm_{grid_id}",
|
|
)]
|
|
assert matches(res, expected)
|
|
|
|
# I can do it again
|
|
res = dg2.manage_key_pressed("Enter", None)
|
|
expected = [
|
|
Div(
|
|
Div(
|
|
NotStr('<svg name="CheckboxChecked'),
|
|
cls="icon-bool",
|
|
),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-3-1"),
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-3-2"),
|
|
id=f"tsm_{grid_id}",
|
|
)]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_press_tab_when_not_readonly(dg, grid_id):
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
dg.manage_cell_click(0, 0) # select the top left
|
|
|
|
res = dg.manage_key_pressed("Tab", "Alice")
|
|
expected = [
|
|
Div(
|
|
Div("Alice"),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-0-0"),
|
|
Div(
|
|
Div(Input()),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-1-0"),
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-1-0"),
|
|
id=f"tsm_{grid_id}")
|
|
]
|
|
assert matches(res, expected)
|
|
|
|
res = dg.manage_key_pressed("Tab", "20")
|
|
expected = [
|
|
Div(
|
|
Div("20"),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-1-0",
|
|
),
|
|
Div(
|
|
Div(Input()),
|
|
hx_swap_oob="innerHTML",
|
|
id=f"tc_{grid_id}-0-1",
|
|
),
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-0-1"),
|
|
id=f"tsm_{grid_id}")
|
|
]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_escape_when_not_in_edition_nor_selection(dg):
|
|
dg.get_state().under_edition = None
|
|
result = dg.escape()
|
|
assert result is None
|
|
|
|
|
|
def test_i_can_manage_selection_on_escape_when_cell_selection_mode(dg, grid_id):
|
|
# The cell is selected, but no input
|
|
dg.get_grid_settings()[DG_READ_ONLY] = False
|
|
dg.get_state().selection_mode = DG_SELECTION_MODE_CELL
|
|
res = dg.manage_cell_click(0, 0) # entering edit mode
|
|
|
|
# sanity check that the input element is here, and it's selected
|
|
assert search_elements_by_name(res, "input") is not None
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
res = dg.manage_key_pressed("Escape", None) # first escape : input is gone, but selection is still there
|
|
assert search_elements_by_name(res, "input") is None
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
res = dg.manage_key_pressed("Escape", None) # second escape : input is gone, and no more selection
|
|
assert search_elements_by_name(res, "input") is None
|
|
assert get_selected(res) == []
|
|
|
|
|
|
def test_i_can_navigate_with_arrow_keys(dg, grid_id):
|
|
res = dg.manage_key_pressed("ArrowRight", None)
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
# I cannot go left
|
|
res = dg.manage_key_pressed("ArrowLeft", None)
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
# I cannot go up
|
|
res = dg.manage_key_pressed("ArrowUp", None)
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
res = dg.manage_key_pressed("ArrowRight", None)
|
|
assert get_selected(res) == [("cell", 1, 0)]
|
|
|
|
# I cannot move right
|
|
res = dg.manage_key_pressed("ArrowRight", None)
|
|
assert get_selected(res) == [("cell", 1, 0)]
|
|
|
|
res = dg.manage_key_pressed("ArrowDown", None)
|
|
assert get_selected(res) == [("cell", 1, 1)]
|
|
|
|
# I cannot move down
|
|
res = dg.manage_key_pressed("ArrowDown", None)
|
|
assert get_selected(res) == [("cell", 1, 1)]
|
|
|
|
res = dg.manage_key_pressed("ArrowLeft", None)
|
|
assert get_selected(res) == [("cell", 0, 1)]
|
|
|
|
# I cannot move down
|
|
res = dg.manage_key_pressed("ArrowLeft", None)
|
|
assert get_selected(res) == [("cell", 0, 1)]
|
|
|
|
res = dg.manage_key_pressed("ArrowUp", None)
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
|
|
def test_i_can_navigate_with_arrow_keys_when_the_grid_is_sorted(dg, grid_id):
|
|
dg.sort("name") # asc sort, so nothing is changed
|
|
dg.sort("name") #
|
|
res = dg.manage_key_pressed("ArrowRight", None)
|
|
assert get_selected(res) == [("cell", 0, 1)]
|
|
|
|
res = dg.manage_key_pressed("ArrowDown", None)
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
# I cannot move down
|
|
res = dg.manage_key_pressed("ArrowDown", None)
|
|
assert get_selected(res) == [("cell", 0, 0)]
|
|
|
|
|
|
def test_i_can_navigate_with_arrow_keys_when_the_grid_is_filtered(dg, grid_id):
|
|
dg.filter(FILTER_INPUT_CID, 'Ali') # Apply filter to grid
|
|
dg.get_state().selected = (0, 0)
|
|
res = dg.manage_key_pressed("ArrowDow", None)
|
|
expected = [
|
|
Div(
|
|
Div(selection_type="cell", element_id=f"tc_{grid_id}-0-0"),
|
|
id=f"tsm_{grid_id}",
|
|
)]
|
|
assert matches(res, expected)
|
|
|
|
|
|
def test_i_can_navigate_with_arrow_keys_when_columns_are_hidden(dg2, grid_id):
|
|
dg2.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["age", "city", "student"], "true", "false")
|
|
dg2.get_state().selected = dg2.navigate(None)
|
|
assert dg2.get_state().selected == (0, 0)
|
|
|
|
dg2.get_state().selected = dg2.navigate("ArrowRight")
|
|
assert dg2.get_state().selected == (4, 0)
|
|
|
|
# it's safe to continue in this direction
|
|
dg2.get_state().selected = dg2.navigate("ArrowRight")
|
|
assert dg2.get_state().selected == (4, 0)
|
|
|
|
# I can go back
|
|
dg2.get_state().selected = dg2.navigate("ArrowLeft")
|
|
assert dg2.get_state().selected == (0, 0)
|
|
|
|
|
|
def test_i_cannot_navigate_when_all_columns_are_hidden(dg2, grid_id):
|
|
dg2.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name", "age", "city", "student", "date"], "true", "false")
|
|
assert dg2.navigate(None) is None
|
|
|
|
|
|
def test_manage_grid_settings_i_can_manage_visible(dg, grid_id):
|
|
# sanity check
|
|
assert dg.get_grid_settings()[DG_COLUMNS] == {'age': {'index': 1,
|
|
'safe_index': 1,
|
|
'title': 'Age',
|
|
'type': 'number'},
|
|
'name': {'index': 0,
|
|
'safe_index': 0,
|
|
'title': 'Name',
|
|
'type': 'string'}}
|
|
# I can hide a column
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name"], "true", "false")
|
|
new_state = [(c.col_id, c.visible) for c in dg.get_state().columns]
|
|
assert new_state == [("name", False), ('age', True)]
|
|
|
|
# I can put it back
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name"], "false", "true")
|
|
new_state = [(c.col_id, c.visible) for c in dg.get_state().columns]
|
|
assert new_state == [("name", True), ('age', True)]
|
|
|
|
# I can remove multiple columns
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name", "age"], "true", "false")
|
|
new_state = [(c.col_id, c.visible) for c in dg.get_state().columns]
|
|
assert new_state == [("name", False), ('age', False)]
|
|
|
|
|
|
def test_manage_grid_settings_extra_selected_is_reset_on_visible_change(dg, grid_id):
|
|
dg.get_state().extra_selected = [(DG_SELECTION_MODE_COLUMN, 0)]
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name"], "something", "something")
|
|
|
|
assert dg.get_state().extra_selected == []
|
|
|
|
|
|
def test_selection_and_under_edition_is_reset_when_the_same_column_is_hidden(dg, grid_id):
|
|
dg.get_state().selected = (0, 0)
|
|
dg.get_state().under_edition = (0, 0)
|
|
dg.get_state().previous_under_edition = (0, 0)
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["name"], "true", "False")
|
|
|
|
assert dg.get_state().selected is None
|
|
assert dg.get_state().under_edition is None
|
|
assert dg.get_state().previous_under_edition is None
|
|
|
|
|
|
def test_selection_and_under_edition_is_not_reset_when_other_column_is_hidden(dg, grid_id):
|
|
dg.get_state().selected = (0, 0)
|
|
dg.get_state().under_edition = (0, 0)
|
|
dg.get_state().previous_under_edition = (0, 0)
|
|
dg.manage_grid_state(VISIBLE_KEY, ["age"], "true", "False")
|
|
|
|
assert dg.get_state().selected == (0, 0)
|
|
assert dg.get_state().under_edition == (0, 0)
|
|
assert dg.get_state().previous_under_edition == (0, 0)
|
|
|
|
|
|
def test_context_menu_i_can_hide_a_selected_column(dg, grid_id):
|
|
res = dg.manage_column_click(0, '')
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide column 'Name'",
|
|
"attribute": DG_STATE_COLUMN_VISIBILITY,
|
|
"col_ids": "name",
|
|
"old_value": True,
|
|
"new_value": False,
|
|
}]
|
|
|
|
assert dg.get_state().selected is None
|
|
|
|
|
|
def test_context_menu_i_can_hide_selected_columns(dg, grid_id):
|
|
dg.manage_column_click(0, '')
|
|
res = dg.manage_column_click(1, 'ctrl')
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide columns 'Name', 'Age'",
|
|
"attribute": DG_STATE_COLUMN_VISIBILITY,
|
|
"col_ids": "name,age",
|
|
"old_value": True,
|
|
"new_value": False,
|
|
}]
|
|
|
|
assert dg.get_state().selected is None
|
|
|
|
|
|
def test_context_menu_i_can_show_a_hidden_column(dg2, grid_id):
|
|
# This scenario is misnamed, you cannot have context menu visible by clicking a hidden column
|
|
dg2.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["age"], "true", "false")
|
|
res = dg2.manage_column_click(1, '')
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{'hx-post': '/on_state_changed',
|
|
'data_tooltip': "Show column 'Age'",
|
|
'attribute': DG_STATE_COLUMN_VISIBILITY,
|
|
'col_ids': 'age',
|
|
'old_value': False,
|
|
'new_value': True}]
|
|
|
|
|
|
def test_context_menu_i_can_hide_and_show_columns(dg2, grid_id):
|
|
# More realistic scenario
|
|
dg2.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["age", "city", "student"], "true", "false")
|
|
dg2.manage_column_click(0, '')
|
|
res = dg2.manage_column_click(4, 'shift') # select all the columns
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{'hx-post': '/on_state_changed',
|
|
'data_tooltip': "Show columns 'Age', 'City', 'Student'",
|
|
'attribute': DG_STATE_COLUMN_VISIBILITY,
|
|
'col_ids': 'age,city,student',
|
|
'old_value': False,
|
|
'new_value': True},
|
|
{'hx-post': '/on_state_changed',
|
|
'data_tooltip': "Hide columns 'Name', 'Date'",
|
|
'attribute': DG_STATE_COLUMN_VISIBILITY,
|
|
'col_ids': 'name,date',
|
|
'old_value': True,
|
|
'new_value': False}]
|
|
|
|
|
|
def test_i_an_undo_redo(dg):
|
|
dg.set_table_value(0, 0, "New name 1")
|
|
dg.set_table_value(0, 1, "New name 2")
|
|
dg.set_table_value(1, 0, 99)
|
|
|
|
assert to_array(dg._df) == [['New name 1', 99], ['New name 2', 25]]
|
|
|
|
dg.history_redo() # nothing happen
|
|
assert to_array(dg._df) == [['New name 1', 99], ['New name 2', 25]]
|
|
|
|
dg.history_undo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['New name 2', 25]]
|
|
|
|
dg.history_undo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['Bob', 25]]
|
|
|
|
dg.history_undo()
|
|
assert to_array(dg._df) == [['Alice', 20], ['Bob', 25]]
|
|
|
|
dg.history_undo() # nothing happen
|
|
assert to_array(dg._df) == [['Alice', 20], ['Bob', 25]]
|
|
|
|
dg.history_redo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['Bob', 25]]
|
|
|
|
dg.history_redo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['New name 2', 25]]
|
|
|
|
dg.history_redo()
|
|
assert to_array(dg._df) == [['New name 1', 99], ['New name 2', 25]]
|
|
|
|
|
|
def test_i_can_reset_the_history(dg):
|
|
dg.set_table_value(0, 0, "New name 1")
|
|
dg.set_table_value(0, 1, "New name 2")
|
|
dg.set_table_value(1, 0, 99)
|
|
|
|
dg.history_undo()
|
|
dg.history_undo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['Bob', 25]]
|
|
|
|
dg.set_table_value(1, 1, 10)
|
|
dg.history_redo() # nothing happen (history is reset with the new value)
|
|
assert to_array(dg._df) == [['New name 1', 20], ['Bob', 10]]
|
|
|
|
dg.history_undo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['Bob', 25]]
|
|
|
|
dg.history_redo()
|
|
assert to_array(dg._df) == [['New name 1', 20], ['Bob', 10]]
|
|
|
|
|
|
def test_i_can_compute_settings_when_no_agg_func():
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
'A': {'index': 0},
|
|
'B': {'index': 1},
|
|
}
|
|
}
|
|
result = DataGrid.get_computed_settings(grid_settings)
|
|
expected = {'max_agg_func': 1, 'agg_func_conf': {'A': [None], 'B': [None]}}
|
|
|
|
assert dataclasses.asdict(result) == expected
|
|
|
|
|
|
def test_i_can_compute_settings():
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
'C': {'agg_func': 'max', 'index': 2},
|
|
'D': {'agg_func_2': 'mean', 'index': 3},
|
|
'A': {'agg_func': 'sum', 'agg_func_2': 'max', 'index': 0},
|
|
'B': {'agg_func_1': 'min', 'agg_func_2': 'mean', 'index': 1},
|
|
}
|
|
}
|
|
result = DataGrid.get_computed_settings(grid_settings)
|
|
expected = {'max_agg_func': 2,
|
|
'agg_func_conf': {'A': ['sum', 'max'],
|
|
'B': ['min', 'mean'],
|
|
'C': ['max', None],
|
|
'D': [None, 'mean']}}
|
|
|
|
assert dataclasses.asdict(result) == expected
|
|
|
|
|
|
@pytest.mark.parametrize("agg_func,expected_value", [
|
|
(DG_AGGREGATE_MAX, 25),
|
|
(DG_AGGREGATE_MIN, 20),
|
|
(DG_AGGREGATE_COUNT, 2),
|
|
(DG_AGGREGATE_SUM, 45),
|
|
(DG_AGGREGATE_MEAN, 22.5),
|
|
])
|
|
def test_i_can_aggregate_function(agg_func, expected_value):
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob'],
|
|
'Age': [20, 25]
|
|
})
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
"name": {
|
|
"index": 0,
|
|
"title": "Name",
|
|
},
|
|
"age": {
|
|
"index": 1,
|
|
"title": "Age",
|
|
"agg_func": agg_func,
|
|
}}, }
|
|
dg = DataGrid(df, id=TEST_GRID_ID, grid_settings=grid_settings)
|
|
dg_grid_id = dg.get_grid_id()
|
|
|
|
footer = dg.mk_table_footer()
|
|
|
|
expected = Div(
|
|
Div(
|
|
Div(
|
|
Div(None),
|
|
data_col="name",
|
|
# data_row="-1"
|
|
),
|
|
Div(
|
|
Div(expected_value),
|
|
data_col="age",
|
|
# data_row="-1"
|
|
),
|
|
cls="dt-row dt-footer"
|
|
),
|
|
id=f"tf_{dg_grid_id}",
|
|
)
|
|
|
|
assert matches(footer, expected)
|
|
|
|
|
|
@pytest.mark.parametrize("agg_func,expected_value", [
|
|
(DG_AGGREGATE_FILTERED_MAX, 30),
|
|
(DG_AGGREGATE_FILTERED_MIN, 20),
|
|
(DG_AGGREGATE_FILTERED_COUNT, 3),
|
|
(DG_AGGREGATE_FILTERED_SUM, 75),
|
|
(DG_AGGREGATE_FILTERED_MEAN, 25.0),
|
|
])
|
|
def test_i_can_test_filtered_aggregate(agg_func, expected_value):
|
|
df = pd.DataFrame({
|
|
'Name': ['Alice', 'Bob', 'Charlie', 'David'],
|
|
'Age': [20, 25, 30, 35],
|
|
})
|
|
grid_settings = {
|
|
DG_COLUMNS: {
|
|
"name": {
|
|
"index": 0,
|
|
"title": "Name",
|
|
},
|
|
"age": {
|
|
"index": 1,
|
|
"title": "Age",
|
|
"agg_func": agg_func,
|
|
}}, }
|
|
dg = DataGrid(df, id=TEST_GRID_ID, grid_settings=grid_settings)
|
|
dg.filter("name", ["Alice", "Bob", "Charlie"])
|
|
dg_grid_id = dg.get_grid_id()
|
|
|
|
footer = dg.mk_table_footer()
|
|
|
|
expected = Div(
|
|
Div(
|
|
Div(
|
|
Div(None),
|
|
data_col="name",
|
|
# data_row="-1"
|
|
),
|
|
Div(
|
|
Div(expected_value),
|
|
data_col="age",
|
|
# data_row="-1"
|
|
),
|
|
cls="dt-row dt-footer"
|
|
),
|
|
id=f"tf_{dg_grid_id}",
|
|
)
|
|
|
|
assert matches(footer, expected)
|
|
|
|
|
|
def test_i_can_move_columns(dg, grid_id):
|
|
dg.manage_grid_state(MOVE_KEY, ["name"], "0", "1")
|
|
res = dg.mk_table()
|
|
expected = Div(
|
|
Div(
|
|
Div(Div(Div("Age")), data_col="age"),
|
|
Div(Div(Div("Name")), data_col="name"),
|
|
cls="dt-row dt-header",
|
|
id=f"th_{grid_id}"),
|
|
Div(
|
|
Div(
|
|
Div(Div("20"), data_col="age"),
|
|
Div(Div("Alice"), data_col="name"),
|
|
),
|
|
Div(
|
|
Div(Div("25"), data_col="age"),
|
|
Div(Div("Bob"), data_col="name"),
|
|
),
|
|
id=f"tb_{grid_id}",
|
|
),
|
|
Div(
|
|
Div(
|
|
Div(Div(), data_col="age"),
|
|
Div(Div(), data_col="name"),
|
|
),
|
|
id=f"tf_{grid_id}")
|
|
)
|
|
|
|
table = search_elements_by_name(res, None, {"class": "dt-inner-table"})[0]
|
|
assert matches(table, expected)
|
|
|
|
|
|
def test_i_can_move_columns_multiple_times(dg2, grid_id):
|
|
dg2.manage_grid_state(MOVE_KEY, ["name"], "0", "1")
|
|
columns_ids = [c.col_id for c in dg2.get_state().columns]
|
|
assert columns_ids == ["age", "name", "city", "student", "date"]
|
|
|
|
dg2.manage_grid_state(MOVE_KEY, ["age"], "0", "1")
|
|
columns_ids = [c.col_id for c in dg2.get_state().columns]
|
|
assert columns_ids == ["name", "age", "city", "student", "date"]
|
|
|
|
dg2.manage_grid_state(MOVE_KEY, ["city"], "2", "4")
|
|
columns_ids = [c.col_id for c in dg2.get_state().columns]
|
|
assert columns_ids == ["name", "age", "student", "date", "city"]
|
|
|
|
|
|
def test_order_is_kept_when_column_is_hidden_or_shown(dg2, grid_id):
|
|
dg2.manage_grid_state(MOVE_KEY, ["name"], "0", "2")
|
|
|
|
dg2.manage_grid_state(VISIBLE_KEY, ["age"], "true", "false")
|
|
columns_ids = [c.col_id for c in dg2.get_state().columns]
|
|
assert columns_ids == ["age", "city", "name", "student", "date"]
|
|
|
|
dg2.manage_grid_state(VISIBLE_KEY, ["age"], "false", "true")
|
|
columns_ids = [c.col_id for c in dg2.get_state().columns]
|
|
assert columns_ids == ["age", "city", "name", "student", "date"]
|
|
|
|
|
|
def test_the_correct_column_is_select_when_column_order_is_changed(dg2, grid_id):
|
|
dg2.get_state().selection_mode = DG_SELECTION_MODE_COLUMN
|
|
dg2.manage_grid_state(MOVE_KEY, ["name"], "0", "1")
|
|
res = dg2.manage_cell_click(1, 0)
|
|
selected = get_selected(res)
|
|
|
|
assert selected == [('column', 'age'), ('cell', 1, 0)]
|
|
|
|
|
|
def test_filter_only_apply_to_visible_columns(dg, grid_id):
|
|
dg.manage_grid_state(DG_STATE_COLUMN_VISIBILITY, ["age"], "true", "false")
|
|
|
|
dg.filter("age", ["20"])
|
|
res = dg.mk_table_body()
|
|
values = extract_table_values(res, header=False)
|
|
expected = [
|
|
["Alice"],
|
|
] # since "age" is not visible, but the filter must be kept (as we do with the sort)
|
|
assert values == expected
|
|
|
|
# same if I use the filter all
|
|
dg.filter(FILTER_INPUT_CID, "20")
|
|
res = dg.mk_table_body()
|
|
values = extract_table_values(res, header=False)
|
|
expected = [] # since "age" is not visible there no match
|
|
assert values == expected
|
|
|
|
|
|
def test_i_can_render_rows_indexes(dg, grid_id):
|
|
dg.get_grid_settings()[DG_ROWS_INDEXES] = True
|
|
res = dg.mk_table()
|
|
table = search_elements_by_name(res, None, {"class": "dt-inner-table"})[0]
|
|
|
|
expected = Div(
|
|
Div(
|
|
Div(cls="dt-row-index-cell", data_col="-1"),
|
|
Div(Div(Div("Name")), data_col="name"),
|
|
Div(Div(Div("Age")), data_col="age"),
|
|
id=f"th_{grid_id}"),
|
|
Div(
|
|
Div(
|
|
Div(Div("0"), cls="dt-row-index-cell", data_col="-1"),
|
|
Div(Div("Alice"), data_col="name"),
|
|
Div(Div("20"), data_col="age"),
|
|
),
|
|
Div(
|
|
Div(Div("1"), data_col="-1"),
|
|
Div(Div("Bob"), data_col="name"),
|
|
Div(Div("25"), data_col="age"),
|
|
),
|
|
id=f"tb_{grid_id}",
|
|
),
|
|
Div(
|
|
Div(
|
|
Div(cls="dt-row-index-cell", data_col="-1"),
|
|
Div(Div(), data_col="name"),
|
|
Div(Div(), data_col="age"),
|
|
),
|
|
id=f"tf_{grid_id}")
|
|
)
|
|
|
|
assert matches(table, expected)
|
|
|
|
|
|
def test_i_can_hide_row(dg, grid_id):
|
|
dg.manage_grid_state(DG_STATE_ROW_VISIBILITY, ["0"], "true", "false")
|
|
res = dg.mk_table_body()
|
|
values = extract_table_values(res, header=False)
|
|
expected = [['Bob', '25']]
|
|
assert values == expected
|
|
|
|
|
|
def test_i_can_select_a_row(dg, grid_id):
|
|
res = dg.manage_row_click(0, '')
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 0)]
|
|
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide row 0",
|
|
"attribute": DG_STATE_ROW_VISIBILITY,
|
|
"col_ids": "0",
|
|
"old_value": True,
|
|
"new_value": False,
|
|
}]
|
|
|
|
|
|
def test_i_can_select_and_unselected_rows_using_control_key(dg, grid_id):
|
|
res = dg.manage_row_click(0, 'ctrl')
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 0)]
|
|
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide row 0",
|
|
"attribute": DG_STATE_ROW_VISIBILITY,
|
|
"col_ids": "0",
|
|
"old_value": True,
|
|
"new_value": False,
|
|
}]
|
|
|
|
# If I click again the row is no longer selected
|
|
res = dg.manage_row_click(0, 'ctrl-')
|
|
|
|
selected = get_selected(res)
|
|
assert selected == []
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == []
|
|
|
|
|
|
def test_i_can_select_multiple_rows_using_control_key(dg, grid_id):
|
|
dg.manage_row_click(1, '')
|
|
res = dg.manage_row_click(0, 'ctrl-')
|
|
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 1), ('row', 0)]
|
|
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide rows 0, 1", # the order is always ascending
|
|
"attribute": DG_STATE_ROW_VISIBILITY,
|
|
"col_ids": "0, 1",
|
|
"old_value": True,
|
|
"new_value": False,
|
|
}]
|
|
|
|
|
|
def test_i_can_select_multiple_rows_using_shift_key(dg2, grid_id):
|
|
res = dg2.manage_row_click(3, 'shift-')
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 3)]
|
|
|
|
res = dg2.manage_row_click(0, 'shift-')
|
|
selected = get_selected(res)
|
|
assert selected == [('row', 0), ('row', 1), ('row', 2), ('row', 3)]
|
|
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide rows 0, 1, 2, 3", # the order is always ascending
|
|
"attribute": DG_STATE_ROW_VISIBILITY,
|
|
"col_ids": "0, 1, 2, 3",
|
|
"old_value": True,
|
|
"new_value": False,
|
|
}]
|
|
|
|
|
|
def test_hidden_rows_can_be_made_visible_using_shift_key(dg2, grid_id):
|
|
dg2.manage_grid_state(DG_STATE_ROW_VISIBILITY, ["2"], "true", "false")
|
|
dg2.manage_row_click(0, '')
|
|
res = dg2.manage_row_click(3, 'shift-')
|
|
|
|
context_menus = get_context_menu(res)
|
|
assert context_menus == [
|
|
{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Hide rows 0, 1, 3", # the order is always ascending
|
|
"attribute": DG_STATE_ROW_VISIBILITY,
|
|
"col_ids": "0, 1, 3",
|
|
"old_value": True,
|
|
"new_value": False
|
|
},
|
|
{
|
|
"hx-post": "/on_state_changed",
|
|
"data_tooltip": "Show row 2",
|
|
"attribute": DG_STATE_ROW_VISIBILITY,
|
|
"col_ids": "2",
|
|
"old_value": False,
|
|
"new_value": True
|
|
}]
|