From 756aade79f598968cdfce61064b9bb7ceea8b942 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 16:16:50 +0900 Subject: [PATCH 01/19] delete mainwindows- --- tests/test_main_window.py | 124 --------------- tests/test_proxy.py | 247 ---------------------------- tests/test_range.py | 76 --------- tests/test_register.py | 56 ------- tests/test_selection_model.py | 178 --------------------- tests/test_selection_op.py | 73 --------- tests/test_selections.py | 117 -------------- tests/test_signal_array.py | 82 ---------- tests/test_spreadsheet.py | 175 -------------------- tests/test_table.py | 283 --------------------------------- tests/test_table_group.py | 82 ---------- tests/test_table_only_usage.py | 73 --------- tests/test_table_subset.py | 117 -------------- tests/test_text_formatter.py | 27 ---- tests/test_undo.py | 64 -------- tests/test_validator.py | 47 ------ 16 files changed, 1821 deletions(-) delete mode 100644 tests/test_main_window.py delete mode 100644 tests/test_proxy.py delete mode 100644 tests/test_range.py delete mode 100644 tests/test_register.py delete mode 100644 tests/test_selection_model.py delete mode 100644 tests/test_selection_op.py delete mode 100644 tests/test_selections.py delete mode 100644 tests/test_signal_array.py delete mode 100644 tests/test_spreadsheet.py delete mode 100644 tests/test_table.py delete mode 100644 tests/test_table_group.py delete mode 100644 tests/test_table_only_usage.py delete mode 100644 tests/test_table_subset.py delete mode 100644 tests/test_text_formatter.py delete mode 100644 tests/test_undo.py delete mode 100644 tests/test_validator.py diff --git a/tests/test_main_window.py b/tests/test_main_window.py deleted file mode 100644 index 18857b45..00000000 --- a/tests/test_main_window.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -from unittest.mock import MagicMock -from tabulous import TableViewer, TableViewerWidget -import numpy as np -from ._utils import get_tabwidget_tab_name -import pytest - -test_data = {"a": [1, 2, 3], "b": [4, 5, 6]} - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_add_layers(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - viewer.add_table(test_data, name="Data") - df = viewer.tables[0].data - assert viewer.current_index == 0 - agg = df.agg(["mean", "std"]) - viewer.add_table(agg, name="Data") - assert viewer.current_index == 1 - assert viewer.tables[0].name == "Data" - assert viewer.tables[1].name == "Data-0" - assert np.all(df == viewer.tables[0].data) - assert np.all(agg == viewer.tables[1].data) - viewer.close() - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -@pytest.mark.parametrize("pos", ["top", "left"]) -def test_renaming(viewer_cls: type[TableViewerWidget], pos): - viewer = viewer_cls(tab_position=pos, show=False) - table0 = viewer.add_table(test_data, name="Data") - assert table0.name == "Data" - assert get_tabwidget_tab_name(viewer, 0) == "Data" - table0.name = "Data-0" - assert table0.name == "Data-0" - assert get_tabwidget_tab_name(viewer, 0) == "Data-0" - table1 = viewer.add_table(test_data.copy(), name="Data-1") - assert table0.name == "Data-0" - assert table1.name == "Data-1" - assert get_tabwidget_tab_name(viewer, 0) == "Data-0" - assert get_tabwidget_tab_name(viewer, 1) == "Data-1" - - # name of newly added table will be renamed if there are collision. - table2 = viewer.add_table(test_data.copy(), name="Data-0") - assert table2.name == "Data-2" - assert get_tabwidget_tab_name(viewer, 2) == "Data-2" - - # new name will be renamed if there are collision. - table1.name = "Data-2" - assert table1.name == "Data-3" - assert get_tabwidget_tab_name(viewer, 1) == "Data-3" - - # no need for coercing if the table is already removed. - name = viewer.tables[0].name - del viewer.tables[0] - table1.name = name - assert table1.name == name - assert get_tabwidget_tab_name(viewer, 0) == name - - viewer.close() - -@pytest.mark.parametrize( - "src, dst", - [(0, 1), (1, 0), (0, 2), (2, 0)] -) -def test_move(src: int, dst: int, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - names = ["0", "1", "2"] - for name in names: - viewer.add_spreadsheet(name=name) - viewer.tables.move(src, dst) - assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_register_action(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - @viewer.tables.register - def f(viewer, i): - pass - - @viewer.tables.register("register-test") - def g(viewer, i): - pass - - @viewer.tables.register("Tests > name") - def h(viewer, i): - pass - - viewer.close() - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_components(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=True) - - # BUG: using qtconsole causes segfault on exit... - # assert not viewer.console.visible - # viewer.console.visible = True - # assert viewer.console.visible - # viewer.console.visible = False - # assert not viewer.console.visible - - viewer.toolbar.visible = True - assert viewer.toolbar.visible - viewer.toolbar.visible = False - assert not viewer.toolbar.visible - - viewer.close() - - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_bind_keycombo(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - - mock = MagicMock() - - viewer.keymap.register("T")(mock) - mock.assert_not_called() - viewer.keymap.press_key("T") - mock.assert_called_once() - - with pytest.raises(Exception): - viewer.keymap.register("T")(mock) - viewer.keymap.register("T", overwrite=True)(print) - - viewer.close() diff --git a/tests/test_proxy.py b/tests/test_proxy.py deleted file mode 100644 index c9c8de56..00000000 --- a/tests/test_proxy.py +++ /dev/null @@ -1,247 +0,0 @@ -from tabulous import TableViewer -from tabulous.widgets import Table -import numpy as np -from numpy.testing import assert_equal -import pandas as pd -from pandas.testing import assert_frame_equal -import pytest - - -def assert_table_proxy(table: Table, other: pd.DataFrame): - return assert_frame_equal(table.data[table.proxy.as_indexer()], other) - -def shuffled_arange(n: int, seed=0) -> np.ndarray: - arr = np.arange(n) - rng = np.random.default_rng(seed) - rng.shuffle(arr) - return arr - -@pytest.mark.parametrize("n", [0, 7, 14, 20]) -def test_simple_filter(n, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) - assert table.table_shape == (20, 2) - table.proxy.set(table.data["a"] < n) - assert table.table_shape == (n, 2) - table.proxy.reset() - assert table.table_shape == (20, 2) - -def test_function_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) - filter_func = lambda df: df["a"] < np.median(df["a"]) - table.proxy.set(filter_func) - assert table.table_shape == (10, 2) - assert_table_proxy(table, table.data[filter_func(table.data)]) - table.data = {"a": np.sin(shuffled_arange(30)), "val0": np.zeros(30), "val1": np.ones(30)} - assert table.table_shape == (30, 3) - assert_table_proxy(table, table.data) - table.proxy.set(filter_func) - assert table.table_shape == (15, 3) - assert_table_proxy(table, table.data[filter_func(table.data)]) - table.proxy.set(None) - assert table.table_shape == (30, 3) - assert_table_proxy(table, table.data) - - -def test_expr_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - df = pd.DataFrame({"a": shuffled_arange(20), "b": shuffled_arange(20)**2}) - table = viewer.add_table(df) - repr(table.proxy) # check that it works - table.proxy.filter("a<4") - assert_table_proxy(table, df[df["a"] < 4]) - - # check filter is initialized before updated - table.proxy.filter("a<6") - assert_table_proxy(table, df[df["a"] < 6]) - - table.proxy.filter("a>6") - assert_table_proxy(table, df[df["a"] > 6]) - - table.proxy.filter("a<=6") - assert_table_proxy(table, df[df["a"] <= 6]) - - table.proxy.filter("a>=6") - assert_table_proxy(table, df[df["a"] >= 6]) - - table.proxy.filter("a==6") - assert_table_proxy(table, df[df["a"] == 6]) - - table.proxy.filter("(5 bool: - return isinstance(viewer._qwidget._tablestack.widget(index), QTableGroup) - -@pytest.mark.parametrize("indices", [[0, 1], [2, 0], [0, 1, 2], [0, 2, 3]]) -def test_merge(indices, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - viewer.add_table(df0) - viewer.add_table(df1) - viewer.add_table(df2) - viewer.add_table(df3) - - viewer.tables.tile(indices) - - for i in indices: - assert _is_group(viewer, i) - table = viewer.tables[i] - qtable = viewer._qwidget._tablestack.tableAtIndex(i) - assert table._qwidget is qtable - -def test_merge_error(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - viewer.add_table(df0) - viewer.add_table(df1) - viewer.add_table(df2) - viewer.add_table(df3) - - with pytest.raises(Exception): - viewer.tables.tile([0, 4]) - with pytest.raises(Exception): - viewer.tables.tile([0, 0]) - with pytest.raises(Exception): - viewer.tables.tile([0.0, 4]) - with pytest.raises(Exception): - viewer.tables.tile([1]) - -@pytest.mark.parametrize("indices", [[0, 1], [2, 0]]) -def test_unmerge_2(indices, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - viewer.add_table(df0) - viewer.add_table(df1) - viewer.add_table(df2) - viewer.add_table(df3) - - viewer.tables.tile(indices) - viewer.tables.untile(0) - - for i in [0, 1, 2, 3]: - assert not _is_group(viewer, i) - table = viewer.tables[i] - qtable = viewer._qwidget._tablestack.tableAtIndex(i) - assert table._qwidget is qtable - - viewer.tables.tile(indices) # test merging again just works - -@pytest.mark.parametrize("indices", [[0, 1, 2], [0, 1, 3], [2, 0, 3]]) -def test_unmerge_3(indices, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - viewer.add_table(df0) - viewer.add_table(df1) - viewer.add_table(df2) - viewer.add_table(df3) - - viewer.tables.tile(indices) - viewer.tables.untile(indices) - - for i in [0, 1, 2, 3]: - assert not _is_group(viewer, i) - table = viewer.tables[i] - qtable = viewer._qwidget._tablestack.tableAtIndex(i) - assert table._qwidget is qtable - - viewer.tables.tile(indices) # test merging again just works diff --git a/tests/test_table_only_usage.py b/tests/test_table_only_usage.py deleted file mode 100644 index 047d503c..00000000 --- a/tests/test_table_only_usage.py +++ /dev/null @@ -1,73 +0,0 @@ -from tabulous.widgets import Table, SpreadSheet -from pytestqt.qtbot import QtBot -from qtpy.QtCore import Qt -import numpy as np - -DATA = np.arange(36).reshape(9, 4) - -def test_selection_move(qtbot: QtBot): - table = Table(DATA.copy(), editable=True) - qtbot.addWidget(table.native) - qtbot.keyClick(table.native, Qt.Key.Key_Down) - assert table.selections[0] == (slice(1, 2), slice(0, 1)) - -def test_copy_paste(qtbot: QtBot): - table = Table(DATA.copy(), editable=True) - qtbot.addWidget(table.native) - - # copy - table.selections = [(slice(0, 1), slice(0, 1))] - qtbot.keyClick(table.native, Qt.Key.Key_C, Qt.KeyboardModifier.ControlModifier) - - # paste - table.selections = [(slice(0, 5), slice(1, 2))] - qtbot.keyClick(table.native, Qt.Key.Key_V, Qt.KeyboardModifier.ControlModifier) - - assert (table.data.iloc[0:5, 1:2] == table.data.iloc[0, 0]).all().all() - -def test_cut_paste(qtbot: QtBot): - table = SpreadSheet(DATA.copy(), editable=True) - qtbot.addWidget(table.native) - - # copy - table.selections = [(slice(0, 1), slice(0, 1))] - qtbot.keyClick(table.native, Qt.Key.Key_X, Qt.KeyboardModifier.ControlModifier) - assert table.cell[0, 0] == "" - - # paste - table.selections = [(slice(0, 5), slice(1, 2))] - qtbot.keyClick(table.native, Qt.Key.Key_V, Qt.KeyboardModifier.ControlModifier) - - assert (table.data.iloc[0:5, 1:2] == 0).all().all() - -def test_undo_redo(qtbot: QtBot): - table = Table(DATA.copy(), editable=True) - qtbot.addWidget(table.native) - - assert table.data.iloc[0, 0] == 0 - table.cell[0, 0] = -1 - assert table.data.iloc[0, 0] == -1 - qtbot.keyClick(table.native, Qt.Key.Key_Z, Qt.KeyboardModifier.ControlModifier) - assert table.data.iloc[0, 0] == 0 - qtbot.keyClick(table.native, Qt.Key.Key_Y, Qt.KeyboardModifier.ControlModifier) - assert table.data.iloc[0, 0] == -1 - -def test_erase(qtbot: QtBot): - sheet = SpreadSheet(DATA.copy(), editable=True) - qtbot.addWidget(sheet.native) - - assert sheet.cell[0, 0] != "" - sheet.selections = [(slice(0, 1), slice(0, 1))] - qtbot.keyClick(sheet.native, Qt.Key.Key_Backspace) - assert sheet.cell[0, 0] == "" - sheet.selections = [(slice(0, 1), slice(1, 2))] - assert sheet.cell[0, 1] != "" - qtbot.keyClick(sheet.native, Qt.Key.Key_Delete) - assert sheet.cell[0, 1] == "" - -def test_slot(make_tabulous_viewer): # NOTE: make_tabulous_viewer initializes config - sheet = SpreadSheet({"a": [1, 3, 5]}) - sheet.cell[0, 1] = "&=np.mean(df.iloc[:, 0])" - assert sheet.cell[0, 1] == "3.0" - sheet.cell[0, 0] = 4 - assert sheet.cell[0, 1] == "4.0" diff --git a/tests/test_table_subset.py b/tests/test_table_subset.py deleted file mode 100644 index 85a453d9..00000000 --- a/tests/test_table_subset.py +++ /dev/null @@ -1,117 +0,0 @@ -from tabulous.widgets import Table -import numpy as np -import pandas as pd -import pytest -from pandas.testing import assert_frame_equal, assert_series_equal - -def assert_obj_equal(a, b): - if isinstance(a, pd.DataFrame): - assert_frame_equal(a, b) - else: - assert_series_equal(a, b) - -DATA = pd.DataFrame(np.arange(50, dtype=np.int32).reshape(10, 5), columns=list("ABCDE")) - -@pytest.mark.parametrize( - "sl", - [ - slice(None), - slice(1, 4), - (slice(None), slice(None)), - (slice(None), "A"), - (slice(None), slice("A", "C")), - (slice(None), slice("B", "B")), - (slice(None), slice("C", None)), - (slice(None), slice(None, "B")), - (slice(None), ["B", "D"]), - ([1, 3, 6], "B"), - ] -) -def test_loc_data_equal(sl: "slice | tuple"): - table = Table(DATA) - assert_obj_equal(table.loc[sl].data, table.data.loc[sl]) - -@pytest.mark.parametrize( - "sl", - [ - slice(None), - slice(1, 4), - (slice(None), slice(None)), - (slice(None), 0), - (slice(None), slice(0, 3)), - (slice(None), slice(1, 2)), - (slice(None), slice(3, None)), - (slice(None), slice(None, 3)), - (slice(None), [1, 3]), - ([1, 3, 6], 1), - ] -) -def test_iloc_data_equal(sl: "slice | tuple"): - table = Table(DATA) - assert_obj_equal(table.iloc[sl].data, table.data.iloc[sl]) - -def test_partial_text_color(): - table = Table(DATA) - - assert table["B"].text_color.item() is None - table["B"].text_color.set(interp_from=["red", "blue"]) - assert table["B"].text_color.item() is not None - assert table["B"].text_color.item() is table.text_color["B"] - assert table.cell.text_color[0, 1].equals("red") - - table["B"].text_color.reset() - assert table["B"].text_color.item() is None - -def test_partial_background_color(): - table = Table(DATA) - - assert table["B"].background_color.item() is None - table["B"].background_color.set(interp_from=["red", "blue"]) - assert table["B"].background_color.item() is not None - assert table["B"].background_color.item() is table.background_color["B"] - assert table.cell.background_color[0, 1].equals("red") - - table["B"].background_color.reset() - assert table["B"].background_color.item() is None - -def test_partial_formatter(): - table = Table(DATA) - - assert table["B"].formatter.item() is None - table["B"].formatter.set(lambda x: "test") - assert table["B"].formatter.item() is not None - assert table.cell.text[0, 1] == "test" - - table["B"].formatter.reset() - assert table["B"].formatter.item() is None - -def test_partial_validator(): - table = Table(DATA, editable=True) - - def _raise(x): - raise ValueError - - table["B"].validator.set(_raise) - with pytest.raises(ValueError): - table.cell[0, 1] = "6" - - table["B"].validator.reset() - table.cell[0, 1] = "6" - -def test_set_data_on_series(): - table = Table(DATA, editable=True) - table["A"].data = -np.ones(10, dtype=np.int32) - assert_series_equal(table["A"].data, pd.Series(-np.ones(10), name="A", dtype=np.int32)) - -def test_set_data_on_subset(): - table = Table(DATA, editable=True) - table.iloc[3:6, 1:3].data = -np.ones((3, 2), dtype=np.int32) - assert_frame_equal( - table.iloc[3:6, 1:3].data, - pd.DataFrame( - -np.ones((3, 2)), - index=range(3, 6), - columns=["B", "C"], - dtype=np.int32 - ) - ) diff --git a/tests/test_text_formatter.py b/tests/test_text_formatter.py deleted file mode 100644 index 3f282b1a..00000000 --- a/tests/test_text_formatter.py +++ /dev/null @@ -1,27 +0,0 @@ -from tabulous import TableViewer - -def test_text_formatter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"number": [1, 2, 3], "char": ["a", "b", "c"]}) - assert table.cell.text[0, 0] == "1" - - # set formatter - table.text_formatter("number", lambda x: str(x) + "!") - assert table.cell.text[0, 0] == "1!" - assert table.cell.text[0, 1] == "a" - - # reset formatter - table.text_formatter("number", None) - assert table.cell.text[0, 0] == "1" - -def test_spreadsheet_default_formatter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"number": ["1.2", "1.23456789"]}) - assert sheet.cell.text[0, 0] == "1.2" - assert sheet.cell.text[1, 0] == "1.23456789" - sheet.dtypes.set("number", "float", formatting=False) - assert sheet.cell.text[0, 0] == "1.2" - assert sheet.cell.text[1, 0] == "1.23456789" - sheet.dtypes.set("number", "float", formatting=True) - assert sheet.cell.text[0, 0] == "1.2000" - assert sheet.cell.text[1, 0] == "1.2346" diff --git a/tests/test_undo.py b/tests/test_undo.py deleted file mode 100644 index f6469ecc..00000000 --- a/tests/test_undo.py +++ /dev/null @@ -1,64 +0,0 @@ -from tabulous import TableViewer -import pandas as pd -from pandas.testing import assert_frame_equal -import pytest - -@pytest.mark.parametrize("fname", ["add_table", "add_spreadsheet"]) -def test_undo_set_data_table(fname, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = getattr(viewer, fname)({"a": [1, 2, 3]}) - table.data = {"b": [1, 2, 3, 4, 5]} - assert table.data.columns == ["b"] - assert table.data.shape == (5, 1) - table.undo_manager.undo() - assert table.data.columns == ["a"] - assert table.data.shape == (3, 1) - table.undo_manager.redo() - assert table.data.columns == ["b"] - assert table.data.shape == (5, 1) - -def test_undo_set_data_groupby(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - df = pd.DataFrame({"a": [1, 2, 3, 4, 5], "b": [1, 2, 1, 2, 2]}) - df2 = pd.DataFrame({"a": [5, 6, 7, 8, 9], "b": [1, 2, 1, 1, 2]}) - group = df.groupby("b") - group2 = df2.groupby("b") - table = viewer.add_groupby(group) - table.data = group2 - assert_frame_equal(table.data.count(), group2.count()) - - table.undo_manager.undo() - assert_frame_equal(table.data.count(), group.count()) - - table.undo_manager.redo() - assert_frame_equal(table.data.count(), group2.count()) - -def test_row_span_undo_redo(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_spreadsheet({"a": [1, 2, 3], "b": [1, 2, 3], "c": [0, 0, 0]}) - s = table.index.span[0] - table.index.span[1] = s + 10 - assert table.index.span == [s, s + 10, s] - table.index.remove(1) - assert table.index.span == [s, s] - table.undo_manager.undo() - assert table.index.span == [s, s + 10, s] - table.undo_manager.redo() - assert table.index.span == [s, s] - table.undo_manager.undo() - assert table.index.span == [s, s + 10, s] - -def test_column_span_undo_redo(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_spreadsheet({"a": [1, 2, 3], "b": [1, 2, 3], "c": [0, 0, 0]}) - s = table.columns.span[0] - table.columns.span[1] = s + 10 - assert table.columns.span == [s, s + 10, s] - table.columns.remove(1) - assert table.columns.span == [s, s] - table.undo_manager.undo() - assert table.columns.span == [s, s + 10, s] - table.undo_manager.redo() - assert table.columns.span == [s, s] - table.undo_manager.undo() - assert table.columns.span == [s, s + 10, s] diff --git a/tests/test_validator.py b/tests/test_validator.py deleted file mode 100644 index d4caf64b..00000000 --- a/tests/test_validator.py +++ /dev/null @@ -1,47 +0,0 @@ -from tabulous import TableViewer -import pandas as pd -from pandas.testing import assert_frame_equal -import pytest - -def test_validator(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table( - {"number": [1, 2, 3], "char": ["a", "b", "c"]}, - editable=True, - ) - @table.validator("number") - def _validator(x): - if x < 0: - raise ValueError("Negative numbers are not allowed") - - table.cell[0, 0] = 2 # no error - assert table.cell[0, 0] == 2 - - # validation error - with pytest.raises(ValueError): - table.cell[0, 0] = -1 - assert table.cell[0, 0] == 2 - -def test_validator_on_paste(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table( - {"a": [1, 2, 3], "b": [1, 2, 3]}, - editable=True, - ) - - @table.validator("a") - @table.validator("b") - def _validator(x): - if x < 0: - raise ValueError("Negative numbers are not allowed") - - df = pd.DataFrame({"a": [1, 3, 1], "b": [1, 3, 1]}) - df.to_clipboard(index=False, header=False) - viewer.paste_data([(slice(None), slice(None))]) - assert_frame_equal(table.data, df) - - df_err = pd.DataFrame({"a": [1, 1, 1], "b": [1, -1, 1]}) - df_err.to_clipboard(index=False, header=False) - with pytest.raises(ValueError): - viewer.paste_data([(slice(None), slice(None))]) - assert_frame_equal(table.data, df) # don't change data From 70054295833cf3eff4b22793cdc5945cb672a600 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 16:21:15 +0900 Subject: [PATCH 02/19] switch files --- tests/test_auto_completion.py | 19 -- tests/test_cell_eval.py | 119 ----------- tests/test_cell_ref_eval.py | 361 --------------------------------- tests/test_colormap.py | 133 ------------ tests/test_column_dtype.py | 83 -------- tests/test_commands.py | 9 - tests/test_config.py | 5 - tests/test_copy_paste.py | 89 -------- tests/test_core.py | 25 --- tests/test_dock_widgets.py | 99 --------- tests/test_finder.py | 114 ----------- tests/test_groupby.py | 50 ----- tests/test_keyboard_ops.py | 55 ----- tests/test_keycombo.py | 189 ----------------- tests/test_magicwidget.py | 28 --- tests/test_main_window.py | 124 +++++++++++ tests/test_proxy.py | 247 ++++++++++++++++++++++ tests/test_range.py | 76 +++++++ tests/test_register.py | 56 +++++ tests/test_selection_model.py | 178 ++++++++++++++++ tests/test_selection_op.py | 73 +++++++ tests/test_selections.py | 117 +++++++++++ tests/test_signal_array.py | 82 ++++++++ tests/test_spreadsheet.py | 175 ++++++++++++++++ tests/test_table.py | 283 ++++++++++++++++++++++++++ tests/test_table_group.py | 82 ++++++++ tests/test_table_only_usage.py | 73 +++++++ tests/test_table_subset.py | 117 +++++++++++ tests/test_text_formatter.py | 27 +++ tests/test_undo.py | 64 ++++++ tests/test_validator.py | 47 +++++ 31 files changed, 1821 insertions(+), 1378 deletions(-) delete mode 100644 tests/test_auto_completion.py delete mode 100644 tests/test_cell_eval.py delete mode 100644 tests/test_cell_ref_eval.py delete mode 100644 tests/test_colormap.py delete mode 100644 tests/test_column_dtype.py delete mode 100644 tests/test_commands.py delete mode 100644 tests/test_config.py delete mode 100644 tests/test_copy_paste.py delete mode 100644 tests/test_core.py delete mode 100644 tests/test_dock_widgets.py delete mode 100644 tests/test_finder.py delete mode 100644 tests/test_groupby.py delete mode 100644 tests/test_keyboard_ops.py delete mode 100644 tests/test_keycombo.py delete mode 100644 tests/test_magicwidget.py create mode 100644 tests/test_main_window.py create mode 100644 tests/test_proxy.py create mode 100644 tests/test_range.py create mode 100644 tests/test_register.py create mode 100644 tests/test_selection_model.py create mode 100644 tests/test_selection_op.py create mode 100644 tests/test_selections.py create mode 100644 tests/test_signal_array.py create mode 100644 tests/test_spreadsheet.py create mode 100644 tests/test_table.py create mode 100644 tests/test_table_group.py create mode 100644 tests/test_table_only_usage.py create mode 100644 tests/test_table_subset.py create mode 100644 tests/test_text_formatter.py create mode 100644 tests/test_undo.py create mode 100644 tests/test_validator.py diff --git a/tests/test_auto_completion.py b/tests/test_auto_completion.py deleted file mode 100644 index 01e90725..00000000 --- a/tests/test_auto_completion.py +++ /dev/null @@ -1,19 +0,0 @@ -from tabulous import TableViewer - -def test_table_list_completion(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - viewer.add_table({}, name="A0") - viewer.add_table({}, name="B0") - viewer.add_table({}, name="X") - assert viewer.tables._ipython_key_completions_() == ["A0", "B0", "X"] - -def test_table_completion(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"x": [1, 2], "yy": [4, 3]}) - assert table._ipython_key_completions_() == ["x", "yy"] - -def test_table_subset_completion(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"x": [1, 2], "y": [4, 3], "z": [0, 0]}) - sub = table.iloc[:, 1:] - assert sub._ipython_key_completions_() == ["y", "z"] diff --git a/tests/test_cell_eval.py b/tests/test_cell_eval.py deleted file mode 100644 index 23fb7141..00000000 --- a/tests/test_cell_eval.py +++ /dev/null @@ -1,119 +0,0 @@ -from tabulous import TableViewer -import pandas as pd -import pytest - -def test_set_ndarray(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet() - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("=np.arange(10)", (0, 0)) - assert qtable._focused_widget is not None - editor.eval_and_close() - assert qtable._focused_widget is None - for i in range(10): - assert sheet.data.iloc[i, 0] == i - -def test_column_vector_output(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("=np.cumsum(df['a'][0:3])", (2, 1)) - editor.eval_and_close() - assert sheet.data.iloc[0, 1] == 1 - assert sheet.data.iloc[1, 1] == 4 - assert sheet.data.iloc[2, 1] == 9 - - # check evaluation at an existing column works - editor = qtable._create_eval_editor("=np.cumsum(df['a'][0:3])", (1, 1)) - editor.eval_and_close() - assert sheet.data.iloc[0, 1] == 1 - assert sheet.data.iloc[1, 1] == 4 - assert sheet.data.iloc[2, 1] == 9 - -def test_partial_column_vector_output(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5, 7]}) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("=np.cumsum(df['a'][1:3])", (2, 1)) - editor.eval_and_close() - assert pd.isna(sheet.data.iloc[0, 1]) - assert sheet.data.iloc[1, 1] == 3 - assert sheet.data.iloc[2, 1] == 8 - assert pd.isna(sheet.data.iloc[3, 1]) - - # check evaluation at an existing column works - editor = qtable._create_eval_editor("=np.cumsum(df['a'][1:3])", (1, 1)) - editor.eval_and_close() - assert pd.isna(sheet.data.iloc[0, 1]) - assert sheet.data.iloc[1, 1] == 3 - assert sheet.data.iloc[2, 1] == 8 - assert pd.isna(sheet.data.iloc[3, 1]) - -def test_row_vector_output(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5], "b": [2, 4, 6]}) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 1)) - editor.eval_and_close() - assert sheet.data.iloc[4, 0] == 3.0 - assert sheet.data.iloc[4, 1] == 4.0 - - # check evaluation at an existing column works - editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 0)) - editor.eval_and_close() - assert sheet.data.iloc[4, 0] == 3.0 - assert sheet.data.iloc[4, 1] == 4.0 - -def test_partial_row_vector_output(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5], "b": [2, 4, 6], "c": [7, 8, 9]}) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 1)) - editor.eval_and_close() - assert sheet.data.iloc[4, 0] == 3.0 - assert sheet.data.iloc[4, 1] == 4.0 - assert pd.isna(sheet.data.iloc[4, 2]) - - # check evaluation at an existing column works - editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 0)) - editor.eval_and_close() - assert sheet.data.iloc[4, 0] == 3.0 - assert sheet.data.iloc[4, 1] == 4.0 - assert pd.isna(sheet.data.iloc[4, 2]) - -def test_scalar_output(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("=np.mean(df['a'][0:3])", (2, 1)) - editor.eval_and_close() - assert sheet.data.iloc[2, 1] == 3.0 - - # check evaluation at an existing column works - editor = qtable._create_eval_editor("=np.mean(df['a'][0:3]) + 1", (1, 1)) - editor.eval_and_close() - assert sheet.data.iloc[1, 1] == 4.0 - -def test_updating_namespace(make_tabulous_viewer): - import numpy as np - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) - qtable = sheet.native._qtable_view - with pytest.raises(ValueError): - viewer.cell_namespace.update(np=0) - viewer.cell_namespace.update(mean=np.mean) - editor = qtable._create_eval_editor("=mean(df['a'][0:3])", (2, 1)) - editor.eval_and_close() - assert sheet.data.iloc[2, 1] == 3.0 - -def test_updating_namespace_by_decorator(make_tabulous_viewer): - import numpy as np - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) - qtable = sheet.native._qtable_view - @viewer.cell_namespace.add - def mean(df): - return np.mean(df) - editor = qtable._create_eval_editor("=mean(df['a'][0:3])", (2, 1)) - editor.eval_and_close() - assert sheet.data.iloc[2, 1] == 3.0 diff --git a/tests/test_cell_ref_eval.py b/tests/test_cell_ref_eval.py deleted file mode 100644 index 59f9ae3d..00000000 --- a/tests/test_cell_ref_eval.py +++ /dev/null @@ -1,361 +0,0 @@ -import numpy as np -from tabulous import TableViewer -import pandas as pd -from numpy.testing import assert_allclose, assert_equal -import pytest - -def test_scalar(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(pd.DataFrame({"a": [1, 3, 5]})) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("&=np.mean(df['a'][0:3])", (0, 1)) - assert qtable._focused_widget is not None - editor.eval_and_close() - assert (0, 1) in list(qtable._table_map.keys()) - assert qtable._focused_widget is None - assert sheet.data.iloc[0, 1] == 3.0 - - # changing data triggers re-evaluation - sheet.cell[0, 0] = 4 - assert sheet.data.iloc[0, 1] == 4.0 - sheet.cell[0, 0] = 7 - assert sheet.data.iloc[0, 1] == 5.0 - -def test_delete_ref_by_editing_the_cells(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(pd.DataFrame({"a": [1, 3, 5]})) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("&=np.mean(df['a'][0:3])", (0, 1)) - editor.eval_and_close() - - assert (0, 1) in list(qtable._table_map.keys()) - sheet.cell[0, 2] = "10" - assert (0, 1) in list(qtable._table_map.keys()) - sheet.cell[0, 1] = "10" - assert (0, 1) not in list(qtable._table_map.keys()) - -def test_delete_ref_by_editing_many_cells(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(pd.DataFrame({"a": [1, 3, 5]})) - qtable = sheet.native._qtable_view - editor = qtable._create_eval_editor("&=np.mean(df['a'][0:3])", (0, 1)) - editor.eval_and_close() - - assert (0, 1) in list(qtable._table_map.keys()) - sheet.cell[0:2, 2] = "10" - assert (0, 1) in list(qtable._table_map.keys()) - sheet.cell[0:3, 1] = ["10", "10", "20"] - assert (0, 1) not in list(qtable._table_map.keys()) - -def test_eval_with_no_ref(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet() - sheet.cell[0, 0] = "&=np.arange(5)" - assert len(sheet.cell.ref) == 0 - -def test_1x1_ref_overwritten_by_Nx1_eval(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) - sheet.cell[0, 1] = "&=np.mean(df['a'][0:3])" - assert (0, 1) in sheet.cell.ref - sheet.cell[1, 1] = "&=np.cumsum(df['a'][0:3])" - assert (0, 1) not in sheet.cell.ref - assert (1, 1) in sheet.cell.ref - -def test_eval_undo(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) - sheet.cell[0, 1] = "&=np.mean(df['a'][0:3])" - assert sheet.data.iloc[0, 1] == 2.0 - - sheet.cell[0, 0] = "10" - assert sheet.data.iloc[0, 1] == 5.0 - sheet.undo_manager.undo() - assert sheet.data.iloc[0, 0] == 1 - assert sheet.data.iloc[0, 1] == 2.0 - sheet.undo_manager.redo() - assert sheet.data.iloc[0, 0] == 10 - assert sheet.data.iloc[0, 1] == 5.0 - -def test_eval_undo_with_many_cells(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) - sheet.cell[0, 1] = "&=np.cumsum(df['a'][0:3])" - assert_allclose(sheet.data.iloc[:, 1].values, [1, 3, 6]) - - sheet.cell[0, 0] = "10" - assert_allclose(sheet.data.iloc[:, 1].values, [10, 12, 15]) - sheet.undo_manager.undo() - assert sheet.data.iloc[0, 0] == 1 - assert_allclose(sheet.data.iloc[:, 1].values, [1, 3, 6]) - sheet.undo_manager.redo() - assert sheet.data.iloc[0, 0] == 10 - assert_allclose(sheet.data.iloc[:, 1].values, [10, 12, 15]) - -def test_eval_undo_with_overwrite(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) - sheet.cell[0, 1] = "&=np.mean(df['a'][0:3])" - sheet.cell[1, 1] = "&=np.cumsum(df['a'][0:3])" - assert_allclose(sheet.data.values, [[1, 1], [2, 3], [3, 6]]) - sheet.undo_manager.undo() - assert_allclose(sheet.data.values, [[1, 2], [2, np.nan], [3, np.nan]]) - sheet.undo_manager.undo() - assert_allclose(sheet.data.values, [[1], [2], [3]]) - -@pytest.mark.parametrize( - "expr", - [ - "df['a'][:]", - "df['b'][:]", - "df['a'][:] + df['b'][:]", - "df['a'][:] + df['b'][:].mean()", - "np.cumsum(df['a'][:])", # function that returns a same-length array - "np.mean(df.loc[:, 'a':'b'], axis=1)", # 1D reduction - "df.loc[:, 'a':'b'].mean(axis=1)", # 1D reduction - "np.mean(df.loc[:, 'a':'b'], axis=1) + df['a'][:]", # reduction + array - "df['a'][:].values", # array - ] -) -def test_many_expr(expr: str, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet( - {"a": [1, 2, 3, 4, 5], "b": [5, 4, 3, 4, 5]} - ) - sheet.cell[1, 2] = f"&={expr}" - assert_allclose(sheet.data.iloc[:, 2].values, eval(expr, {"df": sheet.data, "np": np}, {})) - -def test_returns_shorter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet( - {"a": [1, 2, 3, 4, 5], "b": [5, 4, 3, 4, 5]} - ) - sheet.cell[1, 2] = f"&=np.diff(df['a'][:])" - assert_allclose(sheet.data.iloc[:, 2].values, [1, 1, 1, 1, np.nan]) - -def test_called_once(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - count = 0 - @viewer.cell_namespace.add - def func(*_): - nonlocal count - count += 1 - return 0 - - sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) - sheet.cell[0, 1] = "&=func(df['a'][:])" - assert count == 1 - sheet.cell[0, 0] = "4" - assert count == 2 - -def test_ref_after_insert(make_tabulous_viewer): - # 0 0 0 0 0 - # 0 0 0 X 0 <- edit here - # 0 0 0 0 0 - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((3, 5), dtype=np.float32)) - sheet.cell[1, 3] = "&=np.sum(df.iloc[0:2, 0:1]) + 1" - assert sheet.cell[1, 3] == "1.0" - sheet.columns.insert(2, 1) # insert a column - assert sheet.cell[1, 3] == "0.0" - assert sheet.cell[1, 4] == "1.0" - assert (1, 3) not in sheet.cell.ref - assert (1, 4) in sheet.cell.ref - sheet.undo_manager.undo() - assert sheet.cell[1, 3] == "1.0" - assert sheet.cell[1, 4] == "0.0" - assert (1, 3) in sheet.cell.ref - assert (1, 4) not in sheet.cell.ref - - -def test_ref_after_removal(make_tabulous_viewer): - # 0 0 0 0 0 - # 0 0 0 X 0 <- edit here - # 0 0 0 0 0 - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((3, 5), dtype=np.float32)) - sheet.cell[1, 3] = "&=np.sum(df.iloc[0:2, 0:1]) + 1" - assert sheet.cell[1, 3] == "1.0" - sheet.columns.remove(2, 1) # insert a column - assert sheet.cell[1, 3] == "0.0" - assert sheet.cell[1, 2] == "1.0" - assert (1, 3) not in sheet.cell.ref - assert (1, 2) in sheet.cell.ref - sheet.undo_manager.undo() - assert sheet.cell[1, 3] == "1.0" - assert sheet.cell[1, 2] == "0.0" - assert (1, 3) in sheet.cell.ref - assert (1, 2) not in sheet.cell.ref - - -def test_ref_after_removal_of_column(make_tabulous_viewer): - # --- table --- - # 0 0 0 0 0 - # 0 0 0 X 0 and delete the column including X - # 0 0 0 0 0 - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((3, 5), dtype=np.float32)) - sheet.cell[1, 3] = "&=np.sum(df.iloc[0:2, 0:1]) + 1" - assert sheet.cell[1, 3] == "1.0" - sheet.columns.remove(3, 1) # remove a column - assert sheet.cell[1, 3] == "0.0" - assert len(sheet.cell.ref) == 0 - sheet.undo_manager.undo() - assert sheet.cell[1, 3] == "1.0" - assert sheet.cell[1, 2] == "0.0" - assert (1, 3) in sheet.cell.ref - assert (1, 2) not in sheet.cell.ref - - -def test_removing_source(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((3, 1), dtype=np.float32)) - sheet.cell[0, 1] = "&=np.sum(df.iloc[:, 0])" - assert len(sheet.cell.ref) == 1 - sheet.columns.remove(0, 1) # remove a column - assert sheet.data.shape == (3, 1), "wrong shape" - assert len(sheet.cell.ref) == 0, "slot not removed" - - -def test_removing_one_of_two_sources(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((3, 2), dtype=np.float32)) - sheet.cell[0, 2] = "&=np.sum(df.iloc[:, 0]) + np.sum(df.iloc[:, 1])" - assert len(sheet.cell.ref) == 1 - sheet.columns.remove(0, 1) # remove a column - assert sheet.data.shape == (3, 2), "wrong shape" - assert len(sheet.cell.ref) == 0, "slot not removed" - -def test_removing_or_inserting_left_column(make_tabulous_viewer): - # --- table --- - # 0 0 &=np.sum(df.iloc[:, 1:2]) - # 0 0 - # 0 0 - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet([[0., 1.], [1., 2.], [3., 5.]]) - sheet.cell[0, 2] = "&=np.sum(df.iloc[:, 1])" - assert sheet.cell[0, 2] == "8.0" - sheet.columns.remove(0, 1) # remove a column - assert sheet.data.shape == (3, 2) - assert sheet.cell[0, 1] == "8.0" - sheet.cell[1, 0] = 4 - assert sheet.cell[0, 1] == "10.0" - sheet.columns.insert(0, 1) # insert a column - assert sheet.data.shape == (3, 3) - assert sheet.cell[0, 2] == "10.0" - sheet.cell[1, 0] = 5 - assert sheet.cell[0, 2] == "10.0" - -def test_eval_during_sort(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - rng = np.random.default_rng(123) - sheet = viewer.add_spreadsheet(rng.poisson(3, size=(5, 1))) - sheet.proxy.set([1, 3, 2, 4, 0]) - sheet.cell[1, 1] = "&=np.sum(df.iloc[:, 0])" - assert sheet.data_shown.iloc[1, 1] == np.sum(sheet.data.iloc[:, 0]) - sheet.proxy.reset() - assert sheet.data_shown.iloc[3, 1] == np.sum(sheet.data.iloc[:, 0]) - assert (3, 1) in sheet.cell.ref - -def test_eval_during_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - rng = np.random.default_rng(123) - sheet = viewer.add_spreadsheet({"a": [0, 1, 1, 0, 1], "b": rng.poisson(3, size=5)}) - sheet.proxy.filter("a == 1") - sheet.cell[0, 2] = "&=np.sum(df.iloc[:, 1])" - assert sheet.data_shown.iloc[0, 2] == np.sum(sheet.data.iloc[:, 1]) - sheet.proxy.reset() - assert sheet.data_shown.iloc[1, 2] == np.sum(sheet.data.iloc[:, 1]) - assert (1, 2) in sheet.cell.ref - -def test_broadcasting_during_sort(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - rng = np.random.default_rng(123) - sheet = viewer.add_spreadsheet(rng.poisson(3, size=(5, 1))) - order = [1, 3, 2, 4, 0] - sheet.proxy.set(order) - sheet.cell[1, 1] = "&=np.cumsum(df.iloc[:, 0])" - assert_equal(sheet.data_shown.iloc[:, 1].values, np.cumsum(sheet.data.iloc[:, 0])[order].values) - sheet.proxy.reset() - assert_equal(sheet.data_shown.iloc[:, 1].values, np.cumsum(sheet.data.iloc[:, 0]).values) - assert (3, 1) in sheet.cell.ref - -def test_broadcasting_during_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - rng = np.random.default_rng(123) - sheet = viewer.add_spreadsheet({"a": [0, 1, 1, 0, 1], "b": rng.poisson(3, size=5)}) - sheet.proxy.filter("a == 1") - idx = sheet.proxy.as_indexer() - sheet.cell[0, 2] = "&=np.cumsum(df.iloc[:, 1])" - assert_equal(sheet.data_shown.iloc[:, 2].values, np.cumsum(sheet.data.iloc[:, 1])[idx].values) - sheet.proxy.reset() - assert_equal(sheet.data_shown.iloc[:, 2].values, np.cumsum(sheet.data.iloc[:, 1]).values) - assert (1, 2) in sheet.cell.ref - -def _assert_status_equal(s: str, ref: str): - l = len("") - r = len("") - assert s[l:-r] == ref - -def test_status_tip(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((3, 2))) - sheet.cell[0, 1] = "&=np.sum(df.iloc[:, 0])" - sheet.cell[0, 2] = "&=np.sin(df.iloc[:, 0])" - sheet.move_iloc(0, 0) - _assert_status_equal(viewer.status, "") - sheet.move_iloc(0, 1) - _assert_status_equal(viewer.status, "df.iloc[0:1, 1:2] = np.sum(df.iloc[:, 0])") - sheet.move_iloc(1, 1) - _assert_status_equal(viewer.status, "") - sheet.move_iloc(0, 2) - _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") - sheet.move_iloc(1, 2) - _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") - sheet.move_iloc(2, 2) - _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") - -def test_status_tip_with_proxy(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet(np.zeros((5, 2))) - sheet.cell[1, 1] = "&=np.sum(df.iloc[:, 0])" - sheet.cell[0, 2] = "&=np.sin(df.iloc[:, 0])" - sheet.proxy.set([False, True, True, False, True]) - sheet.move_iloc(0, 0) - _assert_status_equal(viewer.status, "") - sheet.move_iloc(0, 1) - _assert_status_equal(viewer.status, "df.iloc[1:2, 1:2] = np.sum(df.iloc[:, 0])") - sheet.move_iloc(1, 1) - _assert_status_equal(viewer.status, "") - sheet.move_iloc(0, 2) - _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") - sheet.move_iloc(1, 2) - _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") - sheet.move_iloc(2, 2) - _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") - -def test_called_when_expanded(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - # check scalar output - sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]]) - sheet.cell[1, 2] = "&=np.sum(df.iloc[:, 1])" - assert sheet.data.iloc[1, 2] == 6 - sheet.cell[3, 1] = "4" - assert sheet.data.iloc[1, 2] == 10 - - # check vector output - sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]]) - sheet.cell[1, 2] = "&=np.cumsum(df.iloc[:, 1])" - assert_equal(sheet.data.iloc[:, 2].values, [1, 3, 6]) - sheet.cell[3, 1] = "4" - assert_equal(sheet.data.iloc[:, 2].values, [1, 3, 6, 10]) - -def test_N(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]]) - sheet.cell[1, 2] = "&=np.sum(df.iloc[:, 1])/N" - assert sheet.data.iloc[1, 2] == 2 - sheet.cell[3, 1] = "4" - assert sheet.data.iloc[1, 2] == 2.5 - sheet.cell[1, 3] = "&=np.zeros(N)" - assert sheet.data.iloc[1, 3] == 0 diff --git a/tests/test_colormap.py b/tests/test_colormap.py deleted file mode 100644 index 42182fc8..00000000 --- a/tests/test_colormap.py +++ /dev/null @@ -1,133 +0,0 @@ -import pandas as pd -from tabulous.widgets import Table -from tabulous.color import normalize_color -import numpy as np - -cmap = { - "a": (255, 0, 0, 255), - "b": (0, 255, 0, 255), - "c": (0, 0, 255, 255), -} - -def _cmap_func(x): - return cmap[x] - -def test_foreground(): - table = Table({"char": ["a", "b", "c"]}) - default_color = table.cell.text_color[0, 0] - - table.text_color.set("char", cmap) - assert table.cell.text_color[0, 0] == normalize_color(cmap["a"]) - assert table.cell.text_color[1, 0] == normalize_color(cmap["b"]) - assert table.cell.text_color[2, 0] == normalize_color(cmap["c"]) - - table.text_color.set("char", None) - assert table.cell.text_color[0, 0] == default_color - assert table.cell.text_color[1, 0] == default_color - assert table.cell.text_color[2, 0] == default_color - - table.text_color.set("char", _cmap_func) - assert table.cell.text_color[0, 0] == normalize_color(cmap["a"]) - assert table.cell.text_color[1, 0] == normalize_color(cmap["b"]) - assert table.cell.text_color[2, 0] == normalize_color(cmap["c"]) - - -def test_background(): - table = Table({"char": ["a", "b", "c"]}) - default_color = table.cell.background_color[0, 0] - - table.background_color.set("char", cmap) - assert table.cell.background_color[0, 0] == normalize_color(cmap["a"]) - assert table.cell.background_color[1, 0] == normalize_color(cmap["b"]) - assert table.cell.background_color[2, 0] == normalize_color(cmap["c"]) - - table.background_color.set("char", None) - assert table.cell.background_color[0, 0] == default_color - assert table.cell.background_color[1, 0] == default_color - assert table.cell.background_color[2, 0] == default_color - - table.background_color.set("char", _cmap_func) - assert table.cell.background_color[0, 0] == normalize_color(cmap["a"]) - assert table.cell.background_color[1, 0] == normalize_color(cmap["b"]) - assert table.cell.background_color[2, 0] == normalize_color(cmap["c"]) - - -def test_linear_interpolation(): - table = Table( - { - "A": np.arange(10), - "B": np.arange(10) > 5, - "C": pd.date_range("2020-01-01", periods=10), - } - ) - table.text_color.set("A", interp_from=["red", "blue"]) - table.text_color.set("B", interp_from=["red", "blue"]) - table.text_color.set("C", interp_from=["red", "blue"]) - assert table.cell.text_color[0, 0] == normalize_color("red") - assert table.cell.text_color[4, 0] == normalize_color((141, 0, 113, 255)) - assert table.cell.text_color[9, 0] == normalize_color("blue") - assert table.cell.text_color[0, 1] == normalize_color("red") - assert table.cell.text_color[9, 1] == normalize_color("blue") - assert table.cell.text_color[0, 2] == normalize_color("red") - assert table.cell.text_color[4, 2] == normalize_color((141, 0, 113, 255)) - assert table.cell.text_color[9, 2] == normalize_color("blue") - -def test_linear_segmented(): - table = Table( - { - "A": np.arange(-3, 4), - "C": pd.date_range("2020-01-01", periods=7), - } - ) - table.text_color.set("A", interp_from=["red", "gray", "blue"]) - table.text_color.set("C", interp_from=["red", "gray", "blue"]) - assert table.cell.text_color[0, 0] == normalize_color("red") - assert table.cell.text_color[3, 0] == normalize_color("gray") - assert table.cell.text_color[6, 0] == normalize_color("blue") - assert table.cell.text_color[0, 1] == normalize_color("red") - assert table.cell.text_color[3, 1] == normalize_color("gray") - assert table.cell.text_color[6, 1] == normalize_color("blue") - - -def test_invert(): - table = Table({"A": np.arange(10)}) - table.text_color.set("A", interp_from=["red", "blue"]) - red = normalize_color("red") - red_inv = tuple(255 - x for x in red[:3]) + (red[3],) - - assert table.cell.text_color[0, 0] == red - table.text_color.invert("A") - assert table.cell.text_color[0, 0] == red_inv - -def test_set_opacity(): - table = Table({"A": np.arange(10)}) - table.text_color.set("A", interp_from=["red", "blue"]) - assert table.cell.text_color[0, 0][3] == 255 - - table.text_color.set_opacity("A", 0.5) - assert table.cell.text_color[0, 0][3] == 127 - - table.text_color.set("A", interp_from=["red", "blue"], opacity=0.5) - assert table.cell.text_color[0, 0][3] == 127 - -def test_adjust_brightness(): - table = Table({"A": np.arange(10)}) - table.text_color.set("A", interp_from=["red", "blue"]) - assert table.cell.text_color[0, 0] == normalize_color("red") - assert table.cell.text_color[9, 0] == normalize_color("blue") - - table.text_color.adjust_brightness("A", 0.5) - assert table.cell.text_color[0, 0] > normalize_color("red") - assert table.cell.text_color[9, 0] > normalize_color("blue") - - table.text_color.adjust_brightness("A", -0.5) - assert table.cell.text_color[0, 0] == normalize_color("red") - assert table.cell.text_color[9, 0] == normalize_color("blue") - - table.text_color.adjust_brightness("A", -0.5) - assert table.cell.text_color[0, 0] < normalize_color("red") - assert table.cell.text_color[9, 0] < normalize_color("blue") - - table.text_color.adjust_brightness("A", 0.5) - assert table.cell.text_color[0, 0] == normalize_color("red") - assert table.cell.text_color[9, 0] == normalize_color("blue") diff --git a/tests/test_column_dtype.py b/tests/test_column_dtype.py deleted file mode 100644 index 68849a99..00000000 --- a/tests/test_column_dtype.py +++ /dev/null @@ -1,83 +0,0 @@ -from tabulous import TableViewer -import numpy as np -import pandas as pd -import pytest -from typing import Callable - -def test_setting_column_dtype(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"number": [1, 2, 3], "char": ["a", "b", "a"]}) - assert sheet.dtypes == {} - - sheet.dtypes["number"] = "float32" - sheet.dtypes["char"] = "category" - assert sheet.dtypes == {"number": "float32", "char": "category"} - - df = sheet.data - assert df.dtypes.iloc[0] == "float32" - assert df.dtypes.iloc[1] == "category" - - sheet.undo_manager.undo() - assert sheet.dtypes == {"number": "float32"} - - sheet.undo_manager.undo() - assert sheet.dtypes == {} - -def test_datetime(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"datetime": ["2020-01-01", "2020-01-02", "2020-01-03"]}) - assert sheet.data.dtypes.iloc[0] == "object" - - sheet.dtypes["datetime"] = "datetime64[ns]" - assert sheet.data.dtypes.iloc[0] == "datetime64[ns]" - -def test_timedelta(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"timedelta": ["1d", "2d", "3d"]}) - assert sheet.data.dtypes.iloc[0] == "object" - - sheet.dtypes["timedelta"] = "timedelta64[ns]" - assert sheet.data.dtypes.iloc[0] == "timedelta64[ns]" - -def test_updating_column_name(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"number": [1, 2, 3], "char": ["a", "b", "a"]}) - - sheet.dtypes["number"] = "float32" - sheet.dtypes["char"] = "category" - - assert sheet.dtypes == {"number": "float32", "char": "category"} - - sheet.columns[0] = "new_name" - - assert sheet.dtypes == {"new_name": "float32", "char": "category"} - -def test_deleting_column_name(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"number": [1, 2, 3], "char": ["a", "b", "a"]}) - - sheet.dtypes["number"] = "float32" - sheet.dtypes["char"] = "category" - - assert sheet.dtypes == {"number": "float32", "char": "category"} - - sheet.columns.remove(0) - - assert sheet.dtypes == {"char": "category"} - -@pytest.mark.parametrize( - "dtype, fn", - [("int", lambda n: pd.Series(np.arange(n), dtype=np.int32)), - ("float", lambda n: pd.Series(np.arange(n), dtype=np.float64)), - ("datetime64[ns]", lambda n: pd.date_range("2020/01/01", periods=n, freq="2D")), - ("timedelta64[ns]", lambda n: pd.timedelta_range("00:00:00", periods=n, freq="60s")), - ("interval", lambda n: pd.interval_range(0, periods=n, freq=1)), - ], -) -def test_can_parse(dtype, fn: Callable[[int], pd.Series], make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - data = fn(5) - sheet = viewer.add_spreadsheet({"c": data}) - sheet.dtypes["c"] = dtype - assert sheet.dtypes["c"] == dtype - assert sheet.data["c"].dtype == dtype diff --git a/tests/test_commands.py b/tests/test_commands.py deleted file mode 100644 index 9f76bb4f..00000000 --- a/tests/test_commands.py +++ /dev/null @@ -1,9 +0,0 @@ -from tabulous import TableViewer, commands as cmds -import numpy as np - -def test_groupby(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({"a": np.arange(20), "b": np.arange(20) // 5}) - sheet.columns.selected = 1 - cmds.column.run_groupby(viewer) - assert len(viewer.tables) == 2 diff --git a/tests/test_config.py b/tests/test_config.py deleted file mode 100644 index bc612719..00000000 --- a/tests/test_config.py +++ /dev/null @@ -1,5 +0,0 @@ -from tabulous import TableViewer - -def test_config_works(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - viewer.config diff --git a/tests/test_copy_paste.py b/tests/test_copy_paste.py deleted file mode 100644 index d358deb3..00000000 --- a/tests/test_copy_paste.py +++ /dev/null @@ -1,89 +0,0 @@ -from tabulous import TableViewer -import numpy as np -from numpy import testing - -def assert_equal(a, b): - return testing.assert_equal(np.asarray(a), np.asarray(b)) - -def test_copy_and_paste_1x1(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({ - "a": [0, 1, 2, 3, 4], - "b": [2, 4, 6, 8, 10], - "c": [-1, -1, -1, -1, -1], - }, editable=True) - sl_src = (2, 2) - sl_dst = (1, 1) - viewer.copy_data([sl_src]) # copy -1 - old_value = table.data.iloc[sl_dst] - copied = table.data.iloc[sl_src] - viewer.paste_data([sl_dst]) # paste -1 - assert table.data.iloc[sl_dst] == copied - - table.undo_manager.undo() - assert table.data.iloc[sl_dst] == old_value - - -def test_copy_and_paste_same_shape(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({ - "a": [0, 1, 2, 3, 4], - "b": [2, 4, 6, 8, 10], - "c": [-1, -1, -1, -1, -1], - }, editable=True) - - sl_src = (slice(3, 5), slice(1, 3)) - sl_dst = (slice(2, 4), slice(0, 2)) - viewer.copy_data([sl_src]) - old_value = table.data.iloc[sl_dst].copy() - copied = table.data.iloc[sl_src].copy() - viewer.paste_data([sl_dst]) - assert_equal(table.data.iloc[sl_dst], copied) - - table.undo_manager.undo() - assert_equal(table.data.iloc[sl_dst], old_value) - - -def test_copy_array_and_paste_single(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({ - "a": [0, 1, 2, 3, 4], - "b": [2, 4, 6, 8, 10], - "c": [-1, -1, -1, -1, -1], - }, editable=True) - - sl_src = (slice(3, 5), slice(1, 3)) - sl_dst = (slice(2, 4), slice(0, 2)) - viewer.copy_data([sl_src]) - old_value = table.data.iloc[sl_dst].copy() - copied = table.data.iloc[sl_src].copy() - viewer.paste_data([(2, 0)]) # paste with single cell selection - assert_equal(table.data.iloc[sl_dst], copied) - - table.undo_manager.undo() - assert_equal(table.data.iloc[sl_dst], old_value) - -def test_paste_with_column_selected(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_spreadsheet({ - "a": [0, 1, 2, 3, 4], - "b": [2, 4, 6, 8, 10], - "c": [-1, -1, -1, -1, -1], - }) - - viewer.copy_data([(slice(0, 5), slice(2, 3))]) - table._qwidget._qtable_view._selection_model.move_to(-1, 0) - viewer.paste_data() - assert_equal(table.data.iloc[:, 0], np.full(5, -1)) - -def test_paste_with_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - sheet = viewer.add_spreadsheet({ - "a": [0, 1, 2, 3, 4], - "b": ["a", "b", "c", "d", "e"], - "c": ["x"] * 5, - }) - viewer.copy_data([(slice(0, 3), slice(2, 3))]) - sheet.proxy.filter("a % 2 == 0") - viewer.paste_data([(slice(0, 3), slice(1, 2))]) - assert_equal(sheet.data.iloc[:, 1], ["x", "b", "x", "d", "x"]) diff --git a/tests/test_core.py b/tests/test_core.py deleted file mode 100644 index 73b712c0..00000000 --- a/tests/test_core.py +++ /dev/null @@ -1,25 +0,0 @@ -import tabulous as tbl -import pandas as pd -from pathlib import Path -import pytest -from glob import glob -import runpy -import warnings - -DATA_PATH = Path(__file__).parent / "data" - -def test_view(): - df = pd.read_csv(DATA_PATH / "test.csv") - tbl.view_table(df).close() - tbl.view_spreadsheet(df).close() - -def test_io(): - tbl.read_csv(DATA_PATH / "test.csv").close() - -@pytest.mark.parametrize( - "fname", [f for f in glob("examples/*.py") if "napari" not in f and "seaborn" not in f] -) -def test_examples(fname): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - runpy.run_path(fname) diff --git a/tests/test_dock_widgets.py b/tests/test_dock_widgets.py deleted file mode 100644 index 2410f99a..00000000 --- a/tests/test_dock_widgets.py +++ /dev/null @@ -1,99 +0,0 @@ -from magicgui import magicgui -from tabulous import TableViewer -from magicgui.widgets import PushButton - -def dataframe_equal(a, b): - """Check two DataFrame (or tuple of DataFrames) are equal.""" - if isinstance(a, tuple): - if a == (): - return a == b - return all(dataframe_equal(a0, b0) for a0, b0 in zip(a, b)) - return a.equals(b) - -def test_add_and_remove(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - name = "NAME" - btn = PushButton(text="test_button", name=name) - dock = viewer.add_dock_widget(btn) - assert dock.windowTitle() == name - assert name in list(viewer._dock_widgets.keys()) - viewer.remove_dock_widget(name) - assert name not in list(viewer._dock_widgets.keys()) - -def test_table_choice(make_tabulous_viewer): - from tabulous.types import TableData - viewer: TableViewer = make_tabulous_viewer() - - @magicgui - def f(df: TableData): - pass - - viewer.add_dock_widget(f) - - assert f["df"].choices == () - - table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") - - assert dataframe_equal(f["df"].choices, (table0.data,)) - value = f["df"].value - assert all(value["a"] == [0, 0]) - assert all(value["b"] == [1, 1]) - - table1 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-1") - assert dataframe_equal(f["df"].choices, (table0.data, table1.data)) - viewer.tables.pop() - assert dataframe_equal(f["df"].choices, (table0.data,)) - viewer.tables.pop() - assert dataframe_equal(f["df"].choices, ()) - -def test_layer_update(make_tabulous_viewer): - from tabulous.types import TableData - viewer: TableViewer = make_tabulous_viewer() - - @magicgui - def f(df: TableData) -> TableData: - return df - - viewer.add_dock_widget(f) - - table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") - - f.call_button.clicked() - assert len(viewer.tables) == 2 - result = viewer.tables[-1] - assert dataframe_equal(result.data, table0.data) - - # second click will not add a new layer - f.call_button.clicked() - assert len(viewer.tables) == 2 - -def test_table_column_choice(make_tabulous_viewer): - from tabulous.types import TableColumn - - viewer: TableViewer = make_tabulous_viewer() - - @magicgui - def f(df: TableColumn): - pass - - viewer.add_dock_widget(f) - - assert f["df"]._dataframe_choices.choices == () - assert f["df"]._column_choices.choices == () - - table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") - - assert len(f["df"]._dataframe_choices.choices) == 1 - assert len(f["df"]._column_choices.choices) == 2 - assert all(f["df"].value == [0, 0]) - - table1 = viewer.add_table({"a": [0, 0, 1]}, name="table-1") - assert len(f["df"]._dataframe_choices.choices) == 2 - assert len(f["df"]._column_choices.choices) == 2 - - assert dataframe_equal(f["df"]._dataframe_choices.value, table0.data) - - del viewer.tables["table-0"] - - assert len(f["df"]._column_choices.choices) == 1 - assert all(f["df"].value == [0, 0, 1]) diff --git a/tests/test_finder.py b/tests/test_finder.py deleted file mode 100644 index a2f9ef36..00000000 --- a/tests/test_finder.py +++ /dev/null @@ -1,114 +0,0 @@ -from tabulous import TableViewer -from tabulous import commands as cmds -import pandas as pd -from ._utils import selection_equal - -def test_find_by_value(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - layer = viewer.add_table( - pd.DataFrame({'a': [1, 2, 3], 'b': [2, 3, 2], 'c': ["a", "2", "2"]}) - ) - finder = cmds.table.show_finder_widget(viewer) - finder.searchBox().setText("2") - - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findNext() - selection_equal(layer.selections, [(0, 1)]) - finder.findNext() - selection_equal(layer.selections, [(2, 1)]) - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findPrevious() - selection_equal(layer.selections, [(2, 1)]) - finder.findPrevious() - selection_equal(layer.selections, [(0, 1)]) - -def test_find_by_text(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - layer = viewer.add_spreadsheet( - pd.DataFrame({'a': ["aa", "bb", "cc"], 'b': ["bc", "cc", "cc"]}) - ) - finder = cmds.table.show_finder_widget(viewer) - finder.searchBox().setText("cc") - - finder.findNext() - selection_equal(layer.selections, [(2, 0)]) - finder.findNext() - selection_equal(layer.selections, [(1, 1)]) - finder.findNext() - selection_equal(layer.selections, [(2, 1)]) - finder.findNext() - selection_equal(layer.selections, [(2, 0)]) - finder.findPrevious() - selection_equal(layer.selections, [(2, 1)]) - finder.findPrevious() - selection_equal(layer.selections, [(1, 1)]) - -def test_find_by_partial_text(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - layer = viewer.add_spreadsheet( - pd.DataFrame({'a': ["aa", "bb", "cc"], 'b': ["bc", "cc", "cb"]}) - ) - finder = cmds.table.show_finder_widget(viewer) - finder.searchBox().setText("b") - finder.cbox_match.setCurrentIndex(2) # partial match - - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findNext() - selection_equal(layer.selections, [(0, 1)]) - finder.findNext() - selection_equal(layer.selections, [(2, 1)]) - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findPrevious() - selection_equal(layer.selections, [(2, 1)]) - finder.findPrevious() - selection_equal(layer.selections, [(0, 1)]) - - -def test_find_by_regex(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - layer = viewer.add_spreadsheet( - pd.DataFrame({'a': ["a123a", "b321b", "c2h2c"], 'b': ["a442a", "1cc2", "a12a"]}) - ) - finder = cmds.table.show_finder_widget(viewer) - finder.searchBox().setText(r"a\d+a") - finder.cbox_match.setCurrentIndex(3) # regex - - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findNext() - selection_equal(layer.selections, [(0, 1)]) - finder.findNext() - selection_equal(layer.selections, [(2, 1)]) - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findPrevious() - selection_equal(layer.selections, [(2, 1)]) - finder.findPrevious() - selection_equal(layer.selections, [(0, 1)]) - - -def test_find_by_eval(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - layer = viewer.add_spreadsheet( - pd.DataFrame({'a': ["0.13", "a", "2.5"], 'b': ["0.32", "-1.2", "0.54"]}) - ) - finder = cmds.table.show_finder_widget(viewer) - finder.searchBox().setText("0 < float(x) < 1") - finder.cbox_match.setCurrentIndex(4) # eval - - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findNext() - selection_equal(layer.selections, [(0, 1)]) - finder.findNext() - selection_equal(layer.selections, [(2, 1)]) - finder.findNext() - selection_equal(layer.selections, [(1, 0)]) - finder.findPrevious() - selection_equal(layer.selections, [(2, 1)]) - finder.findPrevious() - selection_equal(layer.selections, [(0, 1)]) diff --git a/tests/test_groupby.py b/tests/test_groupby.py deleted file mode 100644 index 40a5453c..00000000 --- a/tests/test_groupby.py +++ /dev/null @@ -1,50 +0,0 @@ -from tabulous import TableViewer -import pandas as pd -from pandas.testing import assert_frame_equal - -# group by single column -df0 = pd.DataFrame({ - "a": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "b": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "c": ["A", "B", "A", "A", "B", "A", "A", "B", "A", "B"], -}) - -# group by two columns -df1 = pd.DataFrame({ - "a": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "b": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - "c": ["A", "B", "A", "A", "B", "A", "A", "B", "A", "B"], - "d": ["X", "X", "Y", "Y", "X", "X", "Y", "Y", "Y", "Y"], -}) - -def test_add_groupby_single(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - grouped = df0.groupby("c") - table = viewer.add_groupby(grouped, name="test") - each_df = [x[1] for x in grouped] - assert table.current_group == "A" - assert_frame_equal(each_df[0], table._qwidget.dataShown()) - table.current_group = "B" - assert table.current_group == "B" - assert_frame_equal(each_df[1], table._qwidget.dataShown()) - -def test_add_groupby_double(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_groupby(df1.groupby(["c", "d"])) - assert table.current_group == ("A", "X") - -def test_add_list(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_groupby( - [{"C0": [0, 0], "C1": [1, 1]}, - {"C0": [5, 0], "C1": [6, 1]},] - ) - assert table.current_group == 0 - -def test_add_dict(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_groupby( - {"a": {"C0": [0, 0], "C1": [1, 1]}, - "b": {"C0": [5, 0], "C1": [6, 1]},} - ) - assert table.current_group == "a" diff --git a/tests/test_keyboard_ops.py b/tests/test_keyboard_ops.py deleted file mode 100644 index 7156a331..00000000 --- a/tests/test_keyboard_ops.py +++ /dev/null @@ -1,55 +0,0 @@ -import sys -import pytest -from tabulous import TableViewer -import numpy as np -from pytestqt.qtbot import QtBot -from qtpy import QtWidgets as QtW -from qtpy.QtCore import Qt - -def test_add_spreadsheet_and_move(qtbot: QtBot): - viewer = TableViewer() - qtbot.addWidget(viewer._qwidget) - qtbot.keyClick(viewer._qwidget, Qt.Key.Key_N, Qt.KeyboardModifier.ControlModifier) - assert len(viewer.tables) == 1 - sheet = viewer.current_table - qtbot.keyClick(sheet._qwidget._qtable_view, Qt.Key.Key_0) - qtbot.keyClick(sheet._qwidget, Qt.Key.Key_Down) - assert sheet.data.shape == (1, 1) - assert sheet.cell[0, 0] == "0" - -def test_movements_in_popup(qtbot: QtBot): - if sys.platform == "darwin": - pytest.skip("Skipping test on macOS because it has a different focus policy.") - viewer = TableViewer() - qtbot.addWidget(viewer._qwidget) - sheet = viewer.add_spreadsheet(np.zeros((10, 10))) - - sheet.view_mode = "popup" - popup = QtW.QApplication.focusWidget() - qtbot.keyClick(popup, Qt.Key.Key_Down) - qtbot.keyClick(popup, Qt.Key.Key_1) - qtbot.keyClick(popup, Qt.Key.Key_Down) - qtbot.keyClick(popup, Qt.Key.Key_Escape) - - assert popup is not QtW.QApplication.focusWidget() - assert sheet.view_mode == "normal" - assert sheet.cell[1, 0] == "1" - -@pytest.mark.parametrize( - "mode, key", - [("popup", Qt.Key.Key_P), ("vertical", Qt.Key.Key_V), ("horizontal", Qt.Key.Key_H)] -) -def test_changing_view_mode(qtbot: QtBot, mode, key): - viewer = TableViewer() - qtbot.addWidget(viewer._qwidget) - sheet = viewer.add_spreadsheet(np.zeros((10, 10))) - - assert sheet.view_mode == "normal" - qtbot.keyClick(viewer._qwidget, Qt.Key.Key_K, Qt.KeyboardModifier.ControlModifier) - qtbot.keyClick(viewer._qwidget, key) - - assert sheet.view_mode == mode - - qtbot.keyClick(viewer._qwidget, Qt.Key.Key_K, Qt.KeyboardModifier.ControlModifier) - qtbot.keyClick(viewer._qwidget, Qt.Key.Key_N) - assert sheet.view_mode == "normal" diff --git a/tests/test_keycombo.py b/tests/test_keycombo.py deleted file mode 100644 index 7a253232..00000000 --- a/tests/test_keycombo.py +++ /dev/null @@ -1,189 +0,0 @@ -from tabulous._keymap import QtKeyMap -from unittest.mock import MagicMock -import pytest - -@pytest.mark.parametrize( - "key", - ["Ctrl+C", ["Ctrl+C", "C"], ["Ctrl+C", "Shift+Z", "Alt+O"], "Ctrl+C, Shift+Z, Alt+O"], -) -def test_keypress(key): - mock = MagicMock() - - keymap = QtKeyMap() - keymap.bind(key, mock) - - mock.assert_not_called() - keymap.press_key("Ctrl+@") - mock.assert_not_called() - keymap.press_key(key) - mock.assert_called_once() - - -def test_keycombo_initialization(): - mock = MagicMock() - - keymap = QtKeyMap() - keymap.bind(["A", "B", "C"], mock) - - mock.assert_not_called() - keymap.press_key("A") - mock.assert_not_called() - keymap.press_key("B") - mock.assert_not_called() - keymap.press_key("C") - mock.assert_called_once() - mock.reset_mock() - keymap.press_key("C") # combo initialized - mock.assert_not_called() - keymap.press_key("A") - keymap.press_key("B") - keymap.press_key("B") - keymap.press_key("C") - mock.assert_not_called() - keymap.press_key("A") - keymap.press_key("B") - keymap.press_key("C") - mock.assert_called_once() - - -def test_activated_callback(): - keymap = QtKeyMap() - mock = MagicMock() - - keymap.bind(["Ctrl+C", "Ctrl+V"], lambda: 0) - keymap.bind("Ctrl+C", mock) - keymap.press_key("Ctrl+C") - mock.assert_called_once() - -def test_activate_modifier_only(): - keymap = QtKeyMap() - mock1 = MagicMock() - mock2 = MagicMock() - - keymap.bind(["Alt"], mock1) - keymap.bind(["Alt", "A"], mock2) - - keymap.press_key("Alt") - mock1.assert_called_once() - mock1.reset_mock() - - keymap.press_key("A") - mock2.assert_called_once() - - keymap.press_key("Alt") - mock1.assert_called_once() - mock1.reset_mock() - - # BUG: this is not working - # keymap.press_key("Alt") - # mock1.assert_not_called() - - # keymap.press_key("Alt") - # mock1.assert_called_once() - -# def test_combo_with_conflicted_modifier(): -# """In Qt, Alt is activated before Alt+A is activated.""" - -# keymap = QtKeyMap() -# mock1 = MagicMock() -# mock2 = MagicMock() - -# keymap.bind(["Alt"], mock1) -# keymap.bind(["Alt", "A"], mock2) - -# keymap.press_key("Alt") -# mock1.assert_called_once() - -# keymap.press_key("Alt+A") -# mock2.assert_called_once() - -def test_combo_with_different_modifiers(): - keymap = QtKeyMap() - mock = MagicMock() - - keymap.bind("Ctrl+K, Shift+A", mock) - - keymap.press_key("Ctrl+K") - keymap.press_key("Shift+A") - mock.assert_called_once() - mock.reset_mock() - - keymap.press_key("Shift+A") - mock.assert_not_called() - -def test_deactivated_callback(): - keymap = QtKeyMap() - mock = MagicMock() - - keymap.bind(["Ctrl+C", "Ctrl+V"], lambda: 0) - keymap.bind_deactivated("Ctrl+C", mock) - keymap.press_key("Ctrl+C") - mock.assert_not_called() - keymap.press_key("Ctrl+V") - mock.assert_called_once() - -def test_callback_to_child_map(): - keymap = QtKeyMap() - mock = MagicMock() - - func0 = lambda: mock(0) - func1 = lambda: mock(1) - keymap.bind("Ctrl+C", func0) - keymap.bind(["Ctrl+C", "Ctrl+V"], func1) - keymap.press_key("Ctrl+C") - mock.assert_called_once() - mock.assert_called_with(0) - keymap.press_key("Ctrl+V") - mock.assert_called_with(1) - -@pytest.mark.parametrize("key0", ["Ctrl+C", "Ctrl+K, Ctrl+C"]) -@pytest.mark.parametrize("key1", ["Ctrl+A", "Ctrl+K, Ctrl+A"]) -def test_rebind(key0, key1): - keymap = QtKeyMap() - mock = MagicMock() - - keymap.bind(key0, mock) - keymap.rebind(key0, key1) - - keymap.press_key(key0) - mock.assert_not_called() - - keymap.press_key(key1) - mock.assert_called_once() - -def test_parametric(): - keymap = QtKeyMap() - mock = MagicMock() - - keymap.bind("Ctrl+{}", mock) - keymap.bind("Ctrl+A", lambda: None) - keymap.bind("Ctrl+B, Ctrl+C", lambda: None) - - mock.assert_not_called() - keymap.press_key("Ctrl+A") - mock.assert_not_called() - keymap.press_key("Ctrl+2") - mock.assert_called_with("2") - keymap.press_key("Ctrl+Z") - mock.assert_called_with("Z") - mock.reset_mock() - keymap.press_key("Ctrl+Shift+1") - mock.assert_not_called() - keymap.press_key("Ctrl") - mock.assert_not_called() - keymap.press_key("Ctrl+T") - mock.assert_called_with("T") - mock.reset_mock() - keymap.press_key("Ctrl+B") - mock.assert_not_called() - -def test_parametric_combo(): - keymap = QtKeyMap() - mock = MagicMock() - - keymap.bind("Ctrl+B, Alt+{}", mock) - - keymap.press_key("Ctrl+B") - mock.assert_not_called() - keymap.press_key("Alt+A") - mock.assert_called_with("A") diff --git a/tests/test_magicwidget.py b/tests/test_magicwidget.py deleted file mode 100644 index 91e25d83..00000000 --- a/tests/test_magicwidget.py +++ /dev/null @@ -1,28 +0,0 @@ -from tabulous import MagicTableViewer -from tabulous.widgets import MagicTable -from magicgui.widgets import Container - -def test_properties(): - viewer = MagicTableViewer(name="test", label="label", tooltip="tooltip", visible=True, enabled=False) - assert viewer.visible - viewer.visible = False - assert not viewer.enabled - viewer.enabled = False - assert viewer.name == "test" - viewer.name = "test2" - assert viewer.label == "label" - assert viewer.tooltip == "tooltip" - -def test_containers(): - ctn = Container() - viewer0 = MagicTableViewer(tab_position="top") - viewer1 = MagicTableViewer(tab_position="left") - ctn.append(viewer0) - ctn.append(viewer1) - -def test_table(): - ctn = Container() - table0 = MagicTable({"a": [1, 2, 3]}, name="table_0") - table1 = MagicTable({"b": [3, 2, 1]}, name="table_1") - ctn.append(table0) - ctn.append(table1) diff --git a/tests/test_main_window.py b/tests/test_main_window.py new file mode 100644 index 00000000..18857b45 --- /dev/null +++ b/tests/test_main_window.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from unittest.mock import MagicMock +from tabulous import TableViewer, TableViewerWidget +import numpy as np +from ._utils import get_tabwidget_tab_name +import pytest + +test_data = {"a": [1, 2, 3], "b": [4, 5, 6]} + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_add_layers(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=False) + viewer.add_table(test_data, name="Data") + df = viewer.tables[0].data + assert viewer.current_index == 0 + agg = df.agg(["mean", "std"]) + viewer.add_table(agg, name="Data") + assert viewer.current_index == 1 + assert viewer.tables[0].name == "Data" + assert viewer.tables[1].name == "Data-0" + assert np.all(df == viewer.tables[0].data) + assert np.all(agg == viewer.tables[1].data) + viewer.close() + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +@pytest.mark.parametrize("pos", ["top", "left"]) +def test_renaming(viewer_cls: type[TableViewerWidget], pos): + viewer = viewer_cls(tab_position=pos, show=False) + table0 = viewer.add_table(test_data, name="Data") + assert table0.name == "Data" + assert get_tabwidget_tab_name(viewer, 0) == "Data" + table0.name = "Data-0" + assert table0.name == "Data-0" + assert get_tabwidget_tab_name(viewer, 0) == "Data-0" + table1 = viewer.add_table(test_data.copy(), name="Data-1") + assert table0.name == "Data-0" + assert table1.name == "Data-1" + assert get_tabwidget_tab_name(viewer, 0) == "Data-0" + assert get_tabwidget_tab_name(viewer, 1) == "Data-1" + + # name of newly added table will be renamed if there are collision. + table2 = viewer.add_table(test_data.copy(), name="Data-0") + assert table2.name == "Data-2" + assert get_tabwidget_tab_name(viewer, 2) == "Data-2" + + # new name will be renamed if there are collision. + table1.name = "Data-2" + assert table1.name == "Data-3" + assert get_tabwidget_tab_name(viewer, 1) == "Data-3" + + # no need for coercing if the table is already removed. + name = viewer.tables[0].name + del viewer.tables[0] + table1.name = name + assert table1.name == name + assert get_tabwidget_tab_name(viewer, 0) == name + + viewer.close() + +@pytest.mark.parametrize( + "src, dst", + [(0, 1), (1, 0), (0, 2), (2, 0)] +) +def test_move(src: int, dst: int, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + names = ["0", "1", "2"] + for name in names: + viewer.add_spreadsheet(name=name) + viewer.tables.move(src, dst) + assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_register_action(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=False) + @viewer.tables.register + def f(viewer, i): + pass + + @viewer.tables.register("register-test") + def g(viewer, i): + pass + + @viewer.tables.register("Tests > name") + def h(viewer, i): + pass + + viewer.close() + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_components(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=True) + + # BUG: using qtconsole causes segfault on exit... + # assert not viewer.console.visible + # viewer.console.visible = True + # assert viewer.console.visible + # viewer.console.visible = False + # assert not viewer.console.visible + + viewer.toolbar.visible = True + assert viewer.toolbar.visible + viewer.toolbar.visible = False + assert not viewer.toolbar.visible + + viewer.close() + + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_bind_keycombo(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=False) + + mock = MagicMock() + + viewer.keymap.register("T")(mock) + mock.assert_not_called() + viewer.keymap.press_key("T") + mock.assert_called_once() + + with pytest.raises(Exception): + viewer.keymap.register("T")(mock) + viewer.keymap.register("T", overwrite=True)(print) + + viewer.close() diff --git a/tests/test_proxy.py b/tests/test_proxy.py new file mode 100644 index 00000000..c9c8de56 --- /dev/null +++ b/tests/test_proxy.py @@ -0,0 +1,247 @@ +from tabulous import TableViewer +from tabulous.widgets import Table +import numpy as np +from numpy.testing import assert_equal +import pandas as pd +from pandas.testing import assert_frame_equal +import pytest + + +def assert_table_proxy(table: Table, other: pd.DataFrame): + return assert_frame_equal(table.data[table.proxy.as_indexer()], other) + +def shuffled_arange(n: int, seed=0) -> np.ndarray: + arr = np.arange(n) + rng = np.random.default_rng(seed) + rng.shuffle(arr) + return arr + +@pytest.mark.parametrize("n", [0, 7, 14, 20]) +def test_simple_filter(n, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) + assert table.table_shape == (20, 2) + table.proxy.set(table.data["a"] < n) + assert table.table_shape == (n, 2) + table.proxy.reset() + assert table.table_shape == (20, 2) + +def test_function_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) + filter_func = lambda df: df["a"] < np.median(df["a"]) + table.proxy.set(filter_func) + assert table.table_shape == (10, 2) + assert_table_proxy(table, table.data[filter_func(table.data)]) + table.data = {"a": np.sin(shuffled_arange(30)), "val0": np.zeros(30), "val1": np.ones(30)} + assert table.table_shape == (30, 3) + assert_table_proxy(table, table.data) + table.proxy.set(filter_func) + assert table.table_shape == (15, 3) + assert_table_proxy(table, table.data[filter_func(table.data)]) + table.proxy.set(None) + assert table.table_shape == (30, 3) + assert_table_proxy(table, table.data) + + +def test_expr_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + df = pd.DataFrame({"a": shuffled_arange(20), "b": shuffled_arange(20)**2}) + table = viewer.add_table(df) + repr(table.proxy) # check that it works + table.proxy.filter("a<4") + assert_table_proxy(table, df[df["a"] < 4]) + + # check filter is initialized before updated + table.proxy.filter("a<6") + assert_table_proxy(table, df[df["a"] < 6]) + + table.proxy.filter("a>6") + assert_table_proxy(table, df[df["a"] > 6]) + + table.proxy.filter("a<=6") + assert_table_proxy(table, df[df["a"] <= 6]) + + table.proxy.filter("a>=6") + assert_table_proxy(table, df[df["a"] >= 6]) + + table.proxy.filter("a==6") + assert_table_proxy(table, df[df["a"] == 6]) + + table.proxy.filter("(5 bool: + return isinstance(viewer._qwidget._tablestack.widget(index), QTableGroup) + +@pytest.mark.parametrize("indices", [[0, 1], [2, 0], [0, 1, 2], [0, 2, 3]]) +def test_merge(indices, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.add_table(df0) + viewer.add_table(df1) + viewer.add_table(df2) + viewer.add_table(df3) + + viewer.tables.tile(indices) + + for i in indices: + assert _is_group(viewer, i) + table = viewer.tables[i] + qtable = viewer._qwidget._tablestack.tableAtIndex(i) + assert table._qwidget is qtable + +def test_merge_error(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.add_table(df0) + viewer.add_table(df1) + viewer.add_table(df2) + viewer.add_table(df3) + + with pytest.raises(Exception): + viewer.tables.tile([0, 4]) + with pytest.raises(Exception): + viewer.tables.tile([0, 0]) + with pytest.raises(Exception): + viewer.tables.tile([0.0, 4]) + with pytest.raises(Exception): + viewer.tables.tile([1]) + +@pytest.mark.parametrize("indices", [[0, 1], [2, 0]]) +def test_unmerge_2(indices, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.add_table(df0) + viewer.add_table(df1) + viewer.add_table(df2) + viewer.add_table(df3) + + viewer.tables.tile(indices) + viewer.tables.untile(0) + + for i in [0, 1, 2, 3]: + assert not _is_group(viewer, i) + table = viewer.tables[i] + qtable = viewer._qwidget._tablestack.tableAtIndex(i) + assert table._qwidget is qtable + + viewer.tables.tile(indices) # test merging again just works + +@pytest.mark.parametrize("indices", [[0, 1, 2], [0, 1, 3], [2, 0, 3]]) +def test_unmerge_3(indices, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.add_table(df0) + viewer.add_table(df1) + viewer.add_table(df2) + viewer.add_table(df3) + + viewer.tables.tile(indices) + viewer.tables.untile(indices) + + for i in [0, 1, 2, 3]: + assert not _is_group(viewer, i) + table = viewer.tables[i] + qtable = viewer._qwidget._tablestack.tableAtIndex(i) + assert table._qwidget is qtable + + viewer.tables.tile(indices) # test merging again just works diff --git a/tests/test_table_only_usage.py b/tests/test_table_only_usage.py new file mode 100644 index 00000000..047d503c --- /dev/null +++ b/tests/test_table_only_usage.py @@ -0,0 +1,73 @@ +from tabulous.widgets import Table, SpreadSheet +from pytestqt.qtbot import QtBot +from qtpy.QtCore import Qt +import numpy as np + +DATA = np.arange(36).reshape(9, 4) + +def test_selection_move(qtbot: QtBot): + table = Table(DATA.copy(), editable=True) + qtbot.addWidget(table.native) + qtbot.keyClick(table.native, Qt.Key.Key_Down) + assert table.selections[0] == (slice(1, 2), slice(0, 1)) + +def test_copy_paste(qtbot: QtBot): + table = Table(DATA.copy(), editable=True) + qtbot.addWidget(table.native) + + # copy + table.selections = [(slice(0, 1), slice(0, 1))] + qtbot.keyClick(table.native, Qt.Key.Key_C, Qt.KeyboardModifier.ControlModifier) + + # paste + table.selections = [(slice(0, 5), slice(1, 2))] + qtbot.keyClick(table.native, Qt.Key.Key_V, Qt.KeyboardModifier.ControlModifier) + + assert (table.data.iloc[0:5, 1:2] == table.data.iloc[0, 0]).all().all() + +def test_cut_paste(qtbot: QtBot): + table = SpreadSheet(DATA.copy(), editable=True) + qtbot.addWidget(table.native) + + # copy + table.selections = [(slice(0, 1), slice(0, 1))] + qtbot.keyClick(table.native, Qt.Key.Key_X, Qt.KeyboardModifier.ControlModifier) + assert table.cell[0, 0] == "" + + # paste + table.selections = [(slice(0, 5), slice(1, 2))] + qtbot.keyClick(table.native, Qt.Key.Key_V, Qt.KeyboardModifier.ControlModifier) + + assert (table.data.iloc[0:5, 1:2] == 0).all().all() + +def test_undo_redo(qtbot: QtBot): + table = Table(DATA.copy(), editable=True) + qtbot.addWidget(table.native) + + assert table.data.iloc[0, 0] == 0 + table.cell[0, 0] = -1 + assert table.data.iloc[0, 0] == -1 + qtbot.keyClick(table.native, Qt.Key.Key_Z, Qt.KeyboardModifier.ControlModifier) + assert table.data.iloc[0, 0] == 0 + qtbot.keyClick(table.native, Qt.Key.Key_Y, Qt.KeyboardModifier.ControlModifier) + assert table.data.iloc[0, 0] == -1 + +def test_erase(qtbot: QtBot): + sheet = SpreadSheet(DATA.copy(), editable=True) + qtbot.addWidget(sheet.native) + + assert sheet.cell[0, 0] != "" + sheet.selections = [(slice(0, 1), slice(0, 1))] + qtbot.keyClick(sheet.native, Qt.Key.Key_Backspace) + assert sheet.cell[0, 0] == "" + sheet.selections = [(slice(0, 1), slice(1, 2))] + assert sheet.cell[0, 1] != "" + qtbot.keyClick(sheet.native, Qt.Key.Key_Delete) + assert sheet.cell[0, 1] == "" + +def test_slot(make_tabulous_viewer): # NOTE: make_tabulous_viewer initializes config + sheet = SpreadSheet({"a": [1, 3, 5]}) + sheet.cell[0, 1] = "&=np.mean(df.iloc[:, 0])" + assert sheet.cell[0, 1] == "3.0" + sheet.cell[0, 0] = 4 + assert sheet.cell[0, 1] == "4.0" diff --git a/tests/test_table_subset.py b/tests/test_table_subset.py new file mode 100644 index 00000000..85a453d9 --- /dev/null +++ b/tests/test_table_subset.py @@ -0,0 +1,117 @@ +from tabulous.widgets import Table +import numpy as np +import pandas as pd +import pytest +from pandas.testing import assert_frame_equal, assert_series_equal + +def assert_obj_equal(a, b): + if isinstance(a, pd.DataFrame): + assert_frame_equal(a, b) + else: + assert_series_equal(a, b) + +DATA = pd.DataFrame(np.arange(50, dtype=np.int32).reshape(10, 5), columns=list("ABCDE")) + +@pytest.mark.parametrize( + "sl", + [ + slice(None), + slice(1, 4), + (slice(None), slice(None)), + (slice(None), "A"), + (slice(None), slice("A", "C")), + (slice(None), slice("B", "B")), + (slice(None), slice("C", None)), + (slice(None), slice(None, "B")), + (slice(None), ["B", "D"]), + ([1, 3, 6], "B"), + ] +) +def test_loc_data_equal(sl: "slice | tuple"): + table = Table(DATA) + assert_obj_equal(table.loc[sl].data, table.data.loc[sl]) + +@pytest.mark.parametrize( + "sl", + [ + slice(None), + slice(1, 4), + (slice(None), slice(None)), + (slice(None), 0), + (slice(None), slice(0, 3)), + (slice(None), slice(1, 2)), + (slice(None), slice(3, None)), + (slice(None), slice(None, 3)), + (slice(None), [1, 3]), + ([1, 3, 6], 1), + ] +) +def test_iloc_data_equal(sl: "slice | tuple"): + table = Table(DATA) + assert_obj_equal(table.iloc[sl].data, table.data.iloc[sl]) + +def test_partial_text_color(): + table = Table(DATA) + + assert table["B"].text_color.item() is None + table["B"].text_color.set(interp_from=["red", "blue"]) + assert table["B"].text_color.item() is not None + assert table["B"].text_color.item() is table.text_color["B"] + assert table.cell.text_color[0, 1].equals("red") + + table["B"].text_color.reset() + assert table["B"].text_color.item() is None + +def test_partial_background_color(): + table = Table(DATA) + + assert table["B"].background_color.item() is None + table["B"].background_color.set(interp_from=["red", "blue"]) + assert table["B"].background_color.item() is not None + assert table["B"].background_color.item() is table.background_color["B"] + assert table.cell.background_color[0, 1].equals("red") + + table["B"].background_color.reset() + assert table["B"].background_color.item() is None + +def test_partial_formatter(): + table = Table(DATA) + + assert table["B"].formatter.item() is None + table["B"].formatter.set(lambda x: "test") + assert table["B"].formatter.item() is not None + assert table.cell.text[0, 1] == "test" + + table["B"].formatter.reset() + assert table["B"].formatter.item() is None + +def test_partial_validator(): + table = Table(DATA, editable=True) + + def _raise(x): + raise ValueError + + table["B"].validator.set(_raise) + with pytest.raises(ValueError): + table.cell[0, 1] = "6" + + table["B"].validator.reset() + table.cell[0, 1] = "6" + +def test_set_data_on_series(): + table = Table(DATA, editable=True) + table["A"].data = -np.ones(10, dtype=np.int32) + assert_series_equal(table["A"].data, pd.Series(-np.ones(10), name="A", dtype=np.int32)) + +def test_set_data_on_subset(): + table = Table(DATA, editable=True) + table.iloc[3:6, 1:3].data = -np.ones((3, 2), dtype=np.int32) + assert_frame_equal( + table.iloc[3:6, 1:3].data, + pd.DataFrame( + -np.ones((3, 2)), + index=range(3, 6), + columns=["B", "C"], + dtype=np.int32 + ) + ) diff --git a/tests/test_text_formatter.py b/tests/test_text_formatter.py new file mode 100644 index 00000000..3f282b1a --- /dev/null +++ b/tests/test_text_formatter.py @@ -0,0 +1,27 @@ +from tabulous import TableViewer + +def test_text_formatter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"number": [1, 2, 3], "char": ["a", "b", "c"]}) + assert table.cell.text[0, 0] == "1" + + # set formatter + table.text_formatter("number", lambda x: str(x) + "!") + assert table.cell.text[0, 0] == "1!" + assert table.cell.text[0, 1] == "a" + + # reset formatter + table.text_formatter("number", None) + assert table.cell.text[0, 0] == "1" + +def test_spreadsheet_default_formatter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"number": ["1.2", "1.23456789"]}) + assert sheet.cell.text[0, 0] == "1.2" + assert sheet.cell.text[1, 0] == "1.23456789" + sheet.dtypes.set("number", "float", formatting=False) + assert sheet.cell.text[0, 0] == "1.2" + assert sheet.cell.text[1, 0] == "1.23456789" + sheet.dtypes.set("number", "float", formatting=True) + assert sheet.cell.text[0, 0] == "1.2000" + assert sheet.cell.text[1, 0] == "1.2346" diff --git a/tests/test_undo.py b/tests/test_undo.py new file mode 100644 index 00000000..f6469ecc --- /dev/null +++ b/tests/test_undo.py @@ -0,0 +1,64 @@ +from tabulous import TableViewer +import pandas as pd +from pandas.testing import assert_frame_equal +import pytest + +@pytest.mark.parametrize("fname", ["add_table", "add_spreadsheet"]) +def test_undo_set_data_table(fname, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = getattr(viewer, fname)({"a": [1, 2, 3]}) + table.data = {"b": [1, 2, 3, 4, 5]} + assert table.data.columns == ["b"] + assert table.data.shape == (5, 1) + table.undo_manager.undo() + assert table.data.columns == ["a"] + assert table.data.shape == (3, 1) + table.undo_manager.redo() + assert table.data.columns == ["b"] + assert table.data.shape == (5, 1) + +def test_undo_set_data_groupby(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + df = pd.DataFrame({"a": [1, 2, 3, 4, 5], "b": [1, 2, 1, 2, 2]}) + df2 = pd.DataFrame({"a": [5, 6, 7, 8, 9], "b": [1, 2, 1, 1, 2]}) + group = df.groupby("b") + group2 = df2.groupby("b") + table = viewer.add_groupby(group) + table.data = group2 + assert_frame_equal(table.data.count(), group2.count()) + + table.undo_manager.undo() + assert_frame_equal(table.data.count(), group.count()) + + table.undo_manager.redo() + assert_frame_equal(table.data.count(), group2.count()) + +def test_row_span_undo_redo(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_spreadsheet({"a": [1, 2, 3], "b": [1, 2, 3], "c": [0, 0, 0]}) + s = table.index.span[0] + table.index.span[1] = s + 10 + assert table.index.span == [s, s + 10, s] + table.index.remove(1) + assert table.index.span == [s, s] + table.undo_manager.undo() + assert table.index.span == [s, s + 10, s] + table.undo_manager.redo() + assert table.index.span == [s, s] + table.undo_manager.undo() + assert table.index.span == [s, s + 10, s] + +def test_column_span_undo_redo(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_spreadsheet({"a": [1, 2, 3], "b": [1, 2, 3], "c": [0, 0, 0]}) + s = table.columns.span[0] + table.columns.span[1] = s + 10 + assert table.columns.span == [s, s + 10, s] + table.columns.remove(1) + assert table.columns.span == [s, s] + table.undo_manager.undo() + assert table.columns.span == [s, s + 10, s] + table.undo_manager.redo() + assert table.columns.span == [s, s] + table.undo_manager.undo() + assert table.columns.span == [s, s + 10, s] diff --git a/tests/test_validator.py b/tests/test_validator.py new file mode 100644 index 00000000..d4caf64b --- /dev/null +++ b/tests/test_validator.py @@ -0,0 +1,47 @@ +from tabulous import TableViewer +import pandas as pd +from pandas.testing import assert_frame_equal +import pytest + +def test_validator(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table( + {"number": [1, 2, 3], "char": ["a", "b", "c"]}, + editable=True, + ) + @table.validator("number") + def _validator(x): + if x < 0: + raise ValueError("Negative numbers are not allowed") + + table.cell[0, 0] = 2 # no error + assert table.cell[0, 0] == 2 + + # validation error + with pytest.raises(ValueError): + table.cell[0, 0] = -1 + assert table.cell[0, 0] == 2 + +def test_validator_on_paste(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table( + {"a": [1, 2, 3], "b": [1, 2, 3]}, + editable=True, + ) + + @table.validator("a") + @table.validator("b") + def _validator(x): + if x < 0: + raise ValueError("Negative numbers are not allowed") + + df = pd.DataFrame({"a": [1, 3, 1], "b": [1, 3, 1]}) + df.to_clipboard(index=False, header=False) + viewer.paste_data([(slice(None), slice(None))]) + assert_frame_equal(table.data, df) + + df_err = pd.DataFrame({"a": [1, 1, 1], "b": [1, -1, 1]}) + df_err.to_clipboard(index=False, header=False) + with pytest.raises(ValueError): + viewer.paste_data([(slice(None), slice(None))]) + assert_frame_equal(table.data, df) # don't change data From 3940ad1e938e17fd5df270e81fc1b658bb8b28fe Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 16:29:00 +0900 Subject: [PATCH 03/19] update conftest --- tests/conftest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c5f34559..8e0cf0d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,11 +17,11 @@ def factory(show=False): for viewer in viewers: viewer.close() - @pytest.fixture(scope="session", autouse=True) def session(): from tabulous._utils import init_config, update_config, get_config from tabulous._qt._mainwindow import QMainWindow + from qtpy.QtWidgets import QApplication import gc with init_config(): @@ -41,7 +41,10 @@ def session(): instance.close() instance.deleteLater() + QApplication.closeAllWindows() + QMainWindow._instances.clear() + for i in range(10): + QApplication.processEvents() gc.collect() if QMainWindow._instances: raise RuntimeError("QMainWindow instances not cleaned up!") - QMainWindow._instances.clear() From d04c1d7617f8f5afc8a70214fa158f84206e74af Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 16:37:15 +0900 Subject: [PATCH 04/19] update gc --- tests/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8e0cf0d7..8948d128 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import pytest from weakref import WeakSet +import gc @pytest.fixture def make_tabulous_viewer(qtbot): @@ -16,13 +17,15 @@ def factory(show=False): for viewer in viewers: viewer.close() + viewer.native.deleteLater() + + gc.collect(1) @pytest.fixture(scope="session", autouse=True) def session(): from tabulous._utils import init_config, update_config, get_config from tabulous._qt._mainwindow import QMainWindow from qtpy.QtWidgets import QApplication - import gc with init_config(): cfg = get_config() @@ -43,7 +46,8 @@ def session(): QApplication.closeAllWindows() QMainWindow._instances.clear() - for i in range(10): + N_PROCESS_EVENTS = 10 + for _ in range(N_PROCESS_EVENTS): QApplication.processEvents() gc.collect() if QMainWindow._instances: From 910e67dbbde9370e04431a3b268dacf48366be98 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 16:44:41 +0900 Subject: [PATCH 05/19] try gc.collect() --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8948d128..7fe7215d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,7 +19,7 @@ def factory(show=False): viewer.close() viewer.native.deleteLater() - gc.collect(1) + gc.collect() @pytest.fixture(scope="session", autouse=True) def session(): @@ -46,7 +46,7 @@ def session(): QApplication.closeAllWindows() QMainWindow._instances.clear() - N_PROCESS_EVENTS = 10 + N_PROCESS_EVENTS = 50 for _ in range(N_PROCESS_EVENTS): QApplication.processEvents() gc.collect() From a8d498d280d8926cfec8880f828e6710cedc7618 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 17:00:07 +0900 Subject: [PATCH 06/19] unlink events --- tabulous/widgets/_mainwindow.py | 15 +++++++++++++++ tests/conftest.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tabulous/widgets/_mainwindow.py b/tabulous/widgets/_mainwindow.py index d070c6ec..d15abb65 100644 --- a/tabulous/widgets/_mainwindow.py +++ b/tabulous/widgets/_mainwindow.py @@ -581,6 +581,21 @@ def _pass_pytable(src, index: int, dst): _tablist.events.changed.connect(self.reset_choices) _tablist.events.renamed.connect(self.reset_choices) + def _unlink_events(self): + _tablist = self.tables + _qtablist = self._qwidget._tablestack + + _tablist.events.inserted.disconnect() + _tablist.events.removed.disconnect() + _tablist.events.moved.disconnect() + _tablist.events.renamed.disconnect() + + _qtablist.itemMoved.disconnect() + _qtablist.tableRenamed.disconnect() + _qtablist.tableRemoved.disconnect() + _qtablist.tablePassed.disconnect() + _qtablist.itemDropped.disconnect() + class TableViewerWidget(TableViewerBase): """The non-main table viewer widget.""" diff --git a/tests/conftest.py b/tests/conftest.py index 7fe7215d..7ffd7ac9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,10 +16,10 @@ def factory(show=False): yield factory for viewer in viewers: + viewer._unlink_events() viewer.close() viewer.native.deleteLater() - gc.collect() @pytest.fixture(scope="session", autouse=True) def session(): From 0b07ff994bf060d46046c0f2b1faf698b53e3c4b Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 21:55:39 +0900 Subject: [PATCH 07/19] cleanup on close --- tabulous/_qt/_table/_base/_table_base.py | 13 +++++++++++++ tabulous/widgets/_mainwindow.py | 3 +++ 2 files changed, 16 insertions(+) diff --git a/tabulous/_qt/_table/_base/_table_base.py b/tabulous/_qt/_table/_base/_table_base.py index ed13d942..f3154582 100644 --- a/tabulous/_qt/_table/_base/_table_base.py +++ b/tabulous/_qt/_table/_base/_table_base.py @@ -230,6 +230,12 @@ def dataShape(self) -> tuple[int, int]: def dataShapeRaw(self) -> tuple[int, int]: return self.getDataFrame().shape + def connectItemChangedSignal(self, *args, **kwargs) -> None: + pass + + def disconnectItemChangedSignal(self): + pass + def zoom(self) -> float: """Get current zoom factor.""" return self._qtable_view.zoom() @@ -1222,6 +1228,13 @@ def connectItemChangedSignal( self.evaluatedSignal.connect(slot_eval) return None + def disconnectItemChangedSignal(self): + self.itemChangedSignal.disconnect() + self.rowChangedSignal.disconnect() + self.columnChangedSignal.disconnect() + self.evaluatedSignal.disconnect() + return None + def keyPressEvent(self, e: QtGui.QKeyEvent): keys = QtKeys(e) if not self._keymap.press_key(keys): diff --git a/tabulous/widgets/_mainwindow.py b/tabulous/widgets/_mainwindow.py index d15abb65..c0a89cf6 100644 --- a/tabulous/widgets/_mainwindow.py +++ b/tabulous/widgets/_mainwindow.py @@ -485,6 +485,9 @@ def paste_data( def close(self): """Close the viewer.""" + for table in self.tables: + table.native.disconnectItemChangedSignal() + self.tables.clear() return self._qwidget.close() @property From f1457d0a393434360b85a98eac3f68bb1b20f34a Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 22:10:53 +0900 Subject: [PATCH 08/19] try removing as_toml --- tabulous/_qt/_mainwindow/_mainwidgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabulous/_qt/_mainwindow/_mainwidgets.py b/tabulous/_qt/_mainwindow/_mainwidgets.py index a816260a..a40f7423 100644 --- a/tabulous/_qt/_mainwindow/_mainwidgets.py +++ b/tabulous/_qt/_mainwindow/_mainwidgets.py @@ -273,7 +273,7 @@ def event(self, e: QEvent): QMainWindow._instances.remove(self) except ValueError: pass - get_config().as_toml() # save config + # get_config().as_toml() # save config elif type in _REORDER_INSTANCES: # upon activation or raise_, put window at the end of _instances From e1bdffd3caf2ea7e47bf1b45ea493be6a41de9c6 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 22:23:58 +0900 Subject: [PATCH 09/19] try removing all the close event --- tabulous/_qt/_mainwindow/_mainwidgets.py | 49 ++--- tests/test_main_window.py | 124 ------------ tests/test_proxy.py | 247 ----------------------- 3 files changed, 25 insertions(+), 395 deletions(-) delete mode 100644 tests/test_main_window.py delete mode 100644 tests/test_proxy.py diff --git a/tabulous/_qt/_mainwindow/_mainwidgets.py b/tabulous/_qt/_mainwindow/_mainwidgets.py index a40f7423..f1d1a1e6 100644 --- a/tabulous/_qt/_mainwindow/_mainwidgets.py +++ b/tabulous/_qt/_mainwindow/_mainwidgets.py @@ -249,31 +249,32 @@ def close(self, ask: bool | None = False) -> bool: def event(self, e: QEvent): type = e.type() if type == QEvent.Type.Close: - if self._ask_on_close and not self._tablestack.isEmpty(): - msgbox = QtW.QMessageBox(self) - msgbox.setWindowTitle("tabulous") - msgbox.setIcon(QtW.QMessageBox.Icon.Question) - msgbox.setText("Are you sure to close this window?") - btn_y = msgbox.addButton(QtW.QMessageBox.StandardButton.Yes) - btn_n = msgbox.addButton(QtW.QMessageBox.StandardButton.No) - btn_y.setText("Close") - btn_n.setText("Cancel") - btn_y.setShortcut(QtGui.QKeySequence("Ctrl+W")) - - cbox = QtW.QCheckBox("Don't ask again") - msgbox.setCheckBox(cbox) - yes = msgbox.exec() - if cbox.isChecked(): - get_config().window.ask_on_close = False - if yes == QtW.QMessageBox.StandardButton.No: - e.ignore() - return True - # when we close the MainWindow, remove it from the instances list - try: - QMainWindow._instances.remove(self) - except ValueError: - pass + # if self._ask_on_close and not self._tablestack.isEmpty(): + # msgbox = QtW.QMessageBox(self) + # msgbox.setWindowTitle("tabulous") + # msgbox.setIcon(QtW.QMessageBox.Icon.Question) + # msgbox.setText("Are you sure to close this window?") + # btn_y = msgbox.addButton(QtW.QMessageBox.StandardButton.Yes) + # btn_n = msgbox.addButton(QtW.QMessageBox.StandardButton.No) + # btn_y.setText("Close") + # btn_n.setText("Cancel") + # btn_y.setShortcut(QtGui.QKeySequence("Ctrl+W")) + + # cbox = QtW.QCheckBox("Don't ask again") + # msgbox.setCheckBox(cbox) + # yes = msgbox.exec() + # if cbox.isChecked(): + # get_config().window.ask_on_close = False + # if yes == QtW.QMessageBox.StandardButton.No: + # e.ignore() + # return True + # # when we close the MainWindow, remove it from the instances list + # try: + # QMainWindow._instances.remove(self) + # except ValueError: + # pass # get_config().as_toml() # save config + pass elif type in _REORDER_INSTANCES: # upon activation or raise_, put window at the end of _instances diff --git a/tests/test_main_window.py b/tests/test_main_window.py deleted file mode 100644 index 18857b45..00000000 --- a/tests/test_main_window.py +++ /dev/null @@ -1,124 +0,0 @@ -from __future__ import annotations - -from unittest.mock import MagicMock -from tabulous import TableViewer, TableViewerWidget -import numpy as np -from ._utils import get_tabwidget_tab_name -import pytest - -test_data = {"a": [1, 2, 3], "b": [4, 5, 6]} - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_add_layers(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - viewer.add_table(test_data, name="Data") - df = viewer.tables[0].data - assert viewer.current_index == 0 - agg = df.agg(["mean", "std"]) - viewer.add_table(agg, name="Data") - assert viewer.current_index == 1 - assert viewer.tables[0].name == "Data" - assert viewer.tables[1].name == "Data-0" - assert np.all(df == viewer.tables[0].data) - assert np.all(agg == viewer.tables[1].data) - viewer.close() - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -@pytest.mark.parametrize("pos", ["top", "left"]) -def test_renaming(viewer_cls: type[TableViewerWidget], pos): - viewer = viewer_cls(tab_position=pos, show=False) - table0 = viewer.add_table(test_data, name="Data") - assert table0.name == "Data" - assert get_tabwidget_tab_name(viewer, 0) == "Data" - table0.name = "Data-0" - assert table0.name == "Data-0" - assert get_tabwidget_tab_name(viewer, 0) == "Data-0" - table1 = viewer.add_table(test_data.copy(), name="Data-1") - assert table0.name == "Data-0" - assert table1.name == "Data-1" - assert get_tabwidget_tab_name(viewer, 0) == "Data-0" - assert get_tabwidget_tab_name(viewer, 1) == "Data-1" - - # name of newly added table will be renamed if there are collision. - table2 = viewer.add_table(test_data.copy(), name="Data-0") - assert table2.name == "Data-2" - assert get_tabwidget_tab_name(viewer, 2) == "Data-2" - - # new name will be renamed if there are collision. - table1.name = "Data-2" - assert table1.name == "Data-3" - assert get_tabwidget_tab_name(viewer, 1) == "Data-3" - - # no need for coercing if the table is already removed. - name = viewer.tables[0].name - del viewer.tables[0] - table1.name = name - assert table1.name == name - assert get_tabwidget_tab_name(viewer, 0) == name - - viewer.close() - -@pytest.mark.parametrize( - "src, dst", - [(0, 1), (1, 0), (0, 2), (2, 0)] -) -def test_move(src: int, dst: int, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - names = ["0", "1", "2"] - for name in names: - viewer.add_spreadsheet(name=name) - viewer.tables.move(src, dst) - assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_register_action(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - @viewer.tables.register - def f(viewer, i): - pass - - @viewer.tables.register("register-test") - def g(viewer, i): - pass - - @viewer.tables.register("Tests > name") - def h(viewer, i): - pass - - viewer.close() - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_components(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=True) - - # BUG: using qtconsole causes segfault on exit... - # assert not viewer.console.visible - # viewer.console.visible = True - # assert viewer.console.visible - # viewer.console.visible = False - # assert not viewer.console.visible - - viewer.toolbar.visible = True - assert viewer.toolbar.visible - viewer.toolbar.visible = False - assert not viewer.toolbar.visible - - viewer.close() - - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_bind_keycombo(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - - mock = MagicMock() - - viewer.keymap.register("T")(mock) - mock.assert_not_called() - viewer.keymap.press_key("T") - mock.assert_called_once() - - with pytest.raises(Exception): - viewer.keymap.register("T")(mock) - viewer.keymap.register("T", overwrite=True)(print) - - viewer.close() diff --git a/tests/test_proxy.py b/tests/test_proxy.py deleted file mode 100644 index c9c8de56..00000000 --- a/tests/test_proxy.py +++ /dev/null @@ -1,247 +0,0 @@ -from tabulous import TableViewer -from tabulous.widgets import Table -import numpy as np -from numpy.testing import assert_equal -import pandas as pd -from pandas.testing import assert_frame_equal -import pytest - - -def assert_table_proxy(table: Table, other: pd.DataFrame): - return assert_frame_equal(table.data[table.proxy.as_indexer()], other) - -def shuffled_arange(n: int, seed=0) -> np.ndarray: - arr = np.arange(n) - rng = np.random.default_rng(seed) - rng.shuffle(arr) - return arr - -@pytest.mark.parametrize("n", [0, 7, 14, 20]) -def test_simple_filter(n, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) - assert table.table_shape == (20, 2) - table.proxy.set(table.data["a"] < n) - assert table.table_shape == (n, 2) - table.proxy.reset() - assert table.table_shape == (20, 2) - -def test_function_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) - filter_func = lambda df: df["a"] < np.median(df["a"]) - table.proxy.set(filter_func) - assert table.table_shape == (10, 2) - assert_table_proxy(table, table.data[filter_func(table.data)]) - table.data = {"a": np.sin(shuffled_arange(30)), "val0": np.zeros(30), "val1": np.ones(30)} - assert table.table_shape == (30, 3) - assert_table_proxy(table, table.data) - table.proxy.set(filter_func) - assert table.table_shape == (15, 3) - assert_table_proxy(table, table.data[filter_func(table.data)]) - table.proxy.set(None) - assert table.table_shape == (30, 3) - assert_table_proxy(table, table.data) - - -def test_expr_filter(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - df = pd.DataFrame({"a": shuffled_arange(20), "b": shuffled_arange(20)**2}) - table = viewer.add_table(df) - repr(table.proxy) # check that it works - table.proxy.filter("a<4") - assert_table_proxy(table, df[df["a"] < 4]) - - # check filter is initialized before updated - table.proxy.filter("a<6") - assert_table_proxy(table, df[df["a"] < 6]) - - table.proxy.filter("a>6") - assert_table_proxy(table, df[df["a"] > 6]) - - table.proxy.filter("a<=6") - assert_table_proxy(table, df[df["a"] <= 6]) - - table.proxy.filter("a>=6") - assert_table_proxy(table, df[df["a"] >= 6]) - - table.proxy.filter("a==6") - assert_table_proxy(table, df[df["a"] == 6]) - - table.proxy.filter("(5 Date: Sun, 24 Sep 2023 22:30:26 +0900 Subject: [PATCH 10/19] try with close event again --- tabulous/_qt/_mainwindow/_mainwidgets.py | 51 ++++++++++++------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/tabulous/_qt/_mainwindow/_mainwidgets.py b/tabulous/_qt/_mainwindow/_mainwidgets.py index f1d1a1e6..a816260a 100644 --- a/tabulous/_qt/_mainwindow/_mainwidgets.py +++ b/tabulous/_qt/_mainwindow/_mainwidgets.py @@ -249,32 +249,31 @@ def close(self, ask: bool | None = False) -> bool: def event(self, e: QEvent): type = e.type() if type == QEvent.Type.Close: - # if self._ask_on_close and not self._tablestack.isEmpty(): - # msgbox = QtW.QMessageBox(self) - # msgbox.setWindowTitle("tabulous") - # msgbox.setIcon(QtW.QMessageBox.Icon.Question) - # msgbox.setText("Are you sure to close this window?") - # btn_y = msgbox.addButton(QtW.QMessageBox.StandardButton.Yes) - # btn_n = msgbox.addButton(QtW.QMessageBox.StandardButton.No) - # btn_y.setText("Close") - # btn_n.setText("Cancel") - # btn_y.setShortcut(QtGui.QKeySequence("Ctrl+W")) - - # cbox = QtW.QCheckBox("Don't ask again") - # msgbox.setCheckBox(cbox) - # yes = msgbox.exec() - # if cbox.isChecked(): - # get_config().window.ask_on_close = False - # if yes == QtW.QMessageBox.StandardButton.No: - # e.ignore() - # return True - # # when we close the MainWindow, remove it from the instances list - # try: - # QMainWindow._instances.remove(self) - # except ValueError: - # pass - # get_config().as_toml() # save config - pass + if self._ask_on_close and not self._tablestack.isEmpty(): + msgbox = QtW.QMessageBox(self) + msgbox.setWindowTitle("tabulous") + msgbox.setIcon(QtW.QMessageBox.Icon.Question) + msgbox.setText("Are you sure to close this window?") + btn_y = msgbox.addButton(QtW.QMessageBox.StandardButton.Yes) + btn_n = msgbox.addButton(QtW.QMessageBox.StandardButton.No) + btn_y.setText("Close") + btn_n.setText("Cancel") + btn_y.setShortcut(QtGui.QKeySequence("Ctrl+W")) + + cbox = QtW.QCheckBox("Don't ask again") + msgbox.setCheckBox(cbox) + yes = msgbox.exec() + if cbox.isChecked(): + get_config().window.ask_on_close = False + if yes == QtW.QMessageBox.StandardButton.No: + e.ignore() + return True + # when we close the MainWindow, remove it from the instances list + try: + QMainWindow._instances.remove(self) + except ValueError: + pass + get_config().as_toml() # save config elif type in _REORDER_INSTANCES: # upon activation or raise_, put window at the end of _instances From 8c9ffe77e6fd650f598f002470384615c9d89e6c Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 22:34:55 +0900 Subject: [PATCH 11/19] try test_main_window --- tests/test_main_window.py | 124 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 tests/test_main_window.py diff --git a/tests/test_main_window.py b/tests/test_main_window.py new file mode 100644 index 00000000..18857b45 --- /dev/null +++ b/tests/test_main_window.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +from unittest.mock import MagicMock +from tabulous import TableViewer, TableViewerWidget +import numpy as np +from ._utils import get_tabwidget_tab_name +import pytest + +test_data = {"a": [1, 2, 3], "b": [4, 5, 6]} + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_add_layers(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=False) + viewer.add_table(test_data, name="Data") + df = viewer.tables[0].data + assert viewer.current_index == 0 + agg = df.agg(["mean", "std"]) + viewer.add_table(agg, name="Data") + assert viewer.current_index == 1 + assert viewer.tables[0].name == "Data" + assert viewer.tables[1].name == "Data-0" + assert np.all(df == viewer.tables[0].data) + assert np.all(agg == viewer.tables[1].data) + viewer.close() + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +@pytest.mark.parametrize("pos", ["top", "left"]) +def test_renaming(viewer_cls: type[TableViewerWidget], pos): + viewer = viewer_cls(tab_position=pos, show=False) + table0 = viewer.add_table(test_data, name="Data") + assert table0.name == "Data" + assert get_tabwidget_tab_name(viewer, 0) == "Data" + table0.name = "Data-0" + assert table0.name == "Data-0" + assert get_tabwidget_tab_name(viewer, 0) == "Data-0" + table1 = viewer.add_table(test_data.copy(), name="Data-1") + assert table0.name == "Data-0" + assert table1.name == "Data-1" + assert get_tabwidget_tab_name(viewer, 0) == "Data-0" + assert get_tabwidget_tab_name(viewer, 1) == "Data-1" + + # name of newly added table will be renamed if there are collision. + table2 = viewer.add_table(test_data.copy(), name="Data-0") + assert table2.name == "Data-2" + assert get_tabwidget_tab_name(viewer, 2) == "Data-2" + + # new name will be renamed if there are collision. + table1.name = "Data-2" + assert table1.name == "Data-3" + assert get_tabwidget_tab_name(viewer, 1) == "Data-3" + + # no need for coercing if the table is already removed. + name = viewer.tables[0].name + del viewer.tables[0] + table1.name = name + assert table1.name == name + assert get_tabwidget_tab_name(viewer, 0) == name + + viewer.close() + +@pytest.mark.parametrize( + "src, dst", + [(0, 1), (1, 0), (0, 2), (2, 0)] +) +def test_move(src: int, dst: int, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + names = ["0", "1", "2"] + for name in names: + viewer.add_spreadsheet(name=name) + viewer.tables.move(src, dst) + assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_register_action(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=False) + @viewer.tables.register + def f(viewer, i): + pass + + @viewer.tables.register("register-test") + def g(viewer, i): + pass + + @viewer.tables.register("Tests > name") + def h(viewer, i): + pass + + viewer.close() + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_components(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=True) + + # BUG: using qtconsole causes segfault on exit... + # assert not viewer.console.visible + # viewer.console.visible = True + # assert viewer.console.visible + # viewer.console.visible = False + # assert not viewer.console.visible + + viewer.toolbar.visible = True + assert viewer.toolbar.visible + viewer.toolbar.visible = False + assert not viewer.toolbar.visible + + viewer.close() + + +@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_bind_keycombo(viewer_cls: type[TableViewerWidget]): + viewer = viewer_cls(show=False) + + mock = MagicMock() + + viewer.keymap.register("T")(mock) + mock.assert_not_called() + viewer.keymap.press_key("T") + mock.assert_called_once() + + with pytest.raises(Exception): + viewer.keymap.register("T")(mock) + viewer.keymap.register("T", overwrite=True)(print) + + viewer.close() From b351dff2cf59a051c8554a1533b160b0dcc1db29 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 22:42:03 +0900 Subject: [PATCH 12/19] comment-out test funcs --- tests/test_main_window.py | 98 +++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/tests/test_main_window.py b/tests/test_main_window.py index 18857b45..c43d25e4 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -8,55 +8,55 @@ test_data = {"a": [1, 2, 3], "b": [4, 5, 6]} -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_add_layers(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) - viewer.add_table(test_data, name="Data") - df = viewer.tables[0].data - assert viewer.current_index == 0 - agg = df.agg(["mean", "std"]) - viewer.add_table(agg, name="Data") - assert viewer.current_index == 1 - assert viewer.tables[0].name == "Data" - assert viewer.tables[1].name == "Data-0" - assert np.all(df == viewer.tables[0].data) - assert np.all(agg == viewer.tables[1].data) - viewer.close() - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -@pytest.mark.parametrize("pos", ["top", "left"]) -def test_renaming(viewer_cls: type[TableViewerWidget], pos): - viewer = viewer_cls(tab_position=pos, show=False) - table0 = viewer.add_table(test_data, name="Data") - assert table0.name == "Data" - assert get_tabwidget_tab_name(viewer, 0) == "Data" - table0.name = "Data-0" - assert table0.name == "Data-0" - assert get_tabwidget_tab_name(viewer, 0) == "Data-0" - table1 = viewer.add_table(test_data.copy(), name="Data-1") - assert table0.name == "Data-0" - assert table1.name == "Data-1" - assert get_tabwidget_tab_name(viewer, 0) == "Data-0" - assert get_tabwidget_tab_name(viewer, 1) == "Data-1" - - # name of newly added table will be renamed if there are collision. - table2 = viewer.add_table(test_data.copy(), name="Data-0") - assert table2.name == "Data-2" - assert get_tabwidget_tab_name(viewer, 2) == "Data-2" - - # new name will be renamed if there are collision. - table1.name = "Data-2" - assert table1.name == "Data-3" - assert get_tabwidget_tab_name(viewer, 1) == "Data-3" - - # no need for coercing if the table is already removed. - name = viewer.tables[0].name - del viewer.tables[0] - table1.name = name - assert table1.name == name - assert get_tabwidget_tab_name(viewer, 0) == name - - viewer.close() +# @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +# def test_add_layers(viewer_cls: type[TableViewerWidget]): +# viewer = viewer_cls(show=False) +# viewer.add_table(test_data, name="Data") +# df = viewer.tables[0].data +# assert viewer.current_index == 0 +# agg = df.agg(["mean", "std"]) +# viewer.add_table(agg, name="Data") +# assert viewer.current_index == 1 +# assert viewer.tables[0].name == "Data" +# assert viewer.tables[1].name == "Data-0" +# assert np.all(df == viewer.tables[0].data) +# assert np.all(agg == viewer.tables[1].data) +# viewer.close() + +# @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +# @pytest.mark.parametrize("pos", ["top", "left"]) +# def test_renaming(viewer_cls: type[TableViewerWidget], pos): +# viewer = viewer_cls(tab_position=pos, show=False) +# table0 = viewer.add_table(test_data, name="Data") +# assert table0.name == "Data" +# assert get_tabwidget_tab_name(viewer, 0) == "Data" +# table0.name = "Data-0" +# assert table0.name == "Data-0" +# assert get_tabwidget_tab_name(viewer, 0) == "Data-0" +# table1 = viewer.add_table(test_data.copy(), name="Data-1") +# assert table0.name == "Data-0" +# assert table1.name == "Data-1" +# assert get_tabwidget_tab_name(viewer, 0) == "Data-0" +# assert get_tabwidget_tab_name(viewer, 1) == "Data-1" + +# # name of newly added table will be renamed if there are collision. +# table2 = viewer.add_table(test_data.copy(), name="Data-0") +# assert table2.name == "Data-2" +# assert get_tabwidget_tab_name(viewer, 2) == "Data-2" + +# # new name will be renamed if there are collision. +# table1.name = "Data-2" +# assert table1.name == "Data-3" +# assert get_tabwidget_tab_name(viewer, 1) == "Data-3" + +# # no need for coercing if the table is already removed. +# name = viewer.tables[0].name +# del viewer.tables[0] +# table1.name = name +# assert table1.name == name +# assert get_tabwidget_tab_name(viewer, 0) == name + +# viewer.close() @pytest.mark.parametrize( "src, dst", From 090b27aef3e3f22f01b7b9e2b47baa58eb2e67e7 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 22:48:10 +0900 Subject: [PATCH 13/19] comment out more --- tests/test_main_window.py | 51 ++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/tests/test_main_window.py b/tests/test_main_window.py index c43d25e4..4c14baac 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -58,17 +58,18 @@ # viewer.close() -@pytest.mark.parametrize( - "src, dst", - [(0, 1), (1, 0), (0, 2), (2, 0)] -) -def test_move(src: int, dst: int, make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - names = ["0", "1", "2"] - for name in names: - viewer.add_spreadsheet(name=name) - viewer.tables.move(src, dst) - assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] +# @pytest.mark.parametrize( +# "src, dst", +# [(0, 1), (1, 0), (0, 2), (2, 0)] +# ) +# def test_move(src: int, dst: int, make_tabulous_viewer): +# viewer: TableViewer = make_tabulous_viewer() +# names = ["0", "1", "2"] +# for name in names: +# viewer.add_spreadsheet(name=name) +# viewer.tables.move(src, dst) +# assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] +# viewer.close() @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) def test_register_action(viewer_cls: type[TableViewerWidget]): @@ -87,23 +88,23 @@ def h(viewer, i): viewer.close() -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_components(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=True) +# @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +# def test_components(viewer_cls: type[TableViewerWidget]): +# viewer = viewer_cls(show=True) - # BUG: using qtconsole causes segfault on exit... - # assert not viewer.console.visible - # viewer.console.visible = True - # assert viewer.console.visible - # viewer.console.visible = False - # assert not viewer.console.visible +# # BUG: using qtconsole causes segfault on exit... +# # assert not viewer.console.visible +# # viewer.console.visible = True +# # assert viewer.console.visible +# # viewer.console.visible = False +# # assert not viewer.console.visible - viewer.toolbar.visible = True - assert viewer.toolbar.visible - viewer.toolbar.visible = False - assert not viewer.toolbar.visible +# viewer.toolbar.visible = True +# assert viewer.toolbar.visible +# viewer.toolbar.visible = False +# assert not viewer.toolbar.visible - viewer.close() +# viewer.close() @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) From a2d0889a241b07d30a0d4ff6042d7edc92444b7d Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 22:59:56 +0900 Subject: [PATCH 14/19] stop using TableViewerWidget --- tests/test_main_window.py | 163 +++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 81 deletions(-) diff --git a/tests/test_main_window.py b/tests/test_main_window.py index 4c14baac..140edce7 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -9,71 +9,71 @@ test_data = {"a": [1, 2, 3], "b": [4, 5, 6]} # @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -# def test_add_layers(viewer_cls: type[TableViewerWidget]): -# viewer = viewer_cls(show=False) -# viewer.add_table(test_data, name="Data") -# df = viewer.tables[0].data -# assert viewer.current_index == 0 -# agg = df.agg(["mean", "std"]) -# viewer.add_table(agg, name="Data") -# assert viewer.current_index == 1 -# assert viewer.tables[0].name == "Data" -# assert viewer.tables[1].name == "Data-0" -# assert np.all(df == viewer.tables[0].data) -# assert np.all(agg == viewer.tables[1].data) -# viewer.close() +def test_add_layers(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.add_table(test_data, name="Data") + df = viewer.tables[0].data + assert viewer.current_index == 0 + agg = df.agg(["mean", "std"]) + viewer.add_table(agg, name="Data") + assert viewer.current_index == 1 + assert viewer.tables[0].name == "Data" + assert viewer.tables[1].name == "Data-0" + assert np.all(df == viewer.tables[0].data) + assert np.all(agg == viewer.tables[1].data) + # viewer.close() # @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -# @pytest.mark.parametrize("pos", ["top", "left"]) -# def test_renaming(viewer_cls: type[TableViewerWidget], pos): -# viewer = viewer_cls(tab_position=pos, show=False) -# table0 = viewer.add_table(test_data, name="Data") -# assert table0.name == "Data" -# assert get_tabwidget_tab_name(viewer, 0) == "Data" -# table0.name = "Data-0" -# assert table0.name == "Data-0" -# assert get_tabwidget_tab_name(viewer, 0) == "Data-0" -# table1 = viewer.add_table(test_data.copy(), name="Data-1") -# assert table0.name == "Data-0" -# assert table1.name == "Data-1" -# assert get_tabwidget_tab_name(viewer, 0) == "Data-0" -# assert get_tabwidget_tab_name(viewer, 1) == "Data-1" - -# # name of newly added table will be renamed if there are collision. -# table2 = viewer.add_table(test_data.copy(), name="Data-0") -# assert table2.name == "Data-2" -# assert get_tabwidget_tab_name(viewer, 2) == "Data-2" - -# # new name will be renamed if there are collision. -# table1.name = "Data-2" -# assert table1.name == "Data-3" -# assert get_tabwidget_tab_name(viewer, 1) == "Data-3" - -# # no need for coercing if the table is already removed. -# name = viewer.tables[0].name -# del viewer.tables[0] -# table1.name = name -# assert table1.name == name -# assert get_tabwidget_tab_name(viewer, 0) == name - -# viewer.close() - -# @pytest.mark.parametrize( -# "src, dst", -# [(0, 1), (1, 0), (0, 2), (2, 0)] -# ) -# def test_move(src: int, dst: int, make_tabulous_viewer): -# viewer: TableViewer = make_tabulous_viewer() -# names = ["0", "1", "2"] -# for name in names: -# viewer.add_spreadsheet(name=name) -# viewer.tables.move(src, dst) -# assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] -# viewer.close() - -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_register_action(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) +@pytest.mark.parametrize("pos", ["top", "left"]) +def test_renaming(make_tabulous_viewer, pos): + viewer: TableViewer = make_tabulous_viewer() + table0 = viewer.add_table(test_data, name="Data") + assert table0.name == "Data" + assert get_tabwidget_tab_name(viewer, 0) == "Data" + table0.name = "Data-0" + assert table0.name == "Data-0" + assert get_tabwidget_tab_name(viewer, 0) == "Data-0" + table1 = viewer.add_table(test_data.copy(), name="Data-1") + assert table0.name == "Data-0" + assert table1.name == "Data-1" + assert get_tabwidget_tab_name(viewer, 0) == "Data-0" + assert get_tabwidget_tab_name(viewer, 1) == "Data-1" + + # name of newly added table will be renamed if there are collision. + table2 = viewer.add_table(test_data.copy(), name="Data-0") + assert table2.name == "Data-2" + assert get_tabwidget_tab_name(viewer, 2) == "Data-2" + + # new name will be renamed if there are collision. + table1.name = "Data-2" + assert table1.name == "Data-3" + assert get_tabwidget_tab_name(viewer, 1) == "Data-3" + + # no need for coercing if the table is already removed. + name = viewer.tables[0].name + del viewer.tables[0] + table1.name = name + assert table1.name == name + assert get_tabwidget_tab_name(viewer, 0) == name + + # viewer.close() + +@pytest.mark.parametrize( + "src, dst", + [(0, 1), (1, 0), (0, 2), (2, 0)] +) +def test_move(src: int, dst: int, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + names = ["0", "1", "2"] + for name in names: + viewer.add_spreadsheet(name=name) + viewer.tables.move(src, dst) + assert [t.name for t in viewer.tables] == [viewer.native._tablestack.tabText(i) for i in range(3)] + # viewer.close() + +# @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_register_action(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() @viewer.tables.register def f(viewer, i): pass @@ -86,30 +86,31 @@ def g(viewer, i): def h(viewer, i): pass - viewer.close() + # viewer.close() # @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -# def test_components(viewer_cls: type[TableViewerWidget]): -# viewer = viewer_cls(show=True) +def test_components(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.show(run=False) -# # BUG: using qtconsole causes segfault on exit... -# # assert not viewer.console.visible -# # viewer.console.visible = True -# # assert viewer.console.visible -# # viewer.console.visible = False -# # assert not viewer.console.visible + # BUG: using qtconsole causes segfault on exit... + # assert not viewer.console.visible + # viewer.console.visible = True + # assert viewer.console.visible + # viewer.console.visible = False + # assert not viewer.console.visible -# viewer.toolbar.visible = True -# assert viewer.toolbar.visible -# viewer.toolbar.visible = False -# assert not viewer.toolbar.visible + viewer.toolbar.visible = True + assert viewer.toolbar.visible + viewer.toolbar.visible = False + assert not viewer.toolbar.visible -# viewer.close() + # viewer.close() -@pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) -def test_bind_keycombo(viewer_cls: type[TableViewerWidget]): - viewer = viewer_cls(show=False) +# @pytest.mark.parametrize("viewer_cls", [TableViewer, TableViewerWidget]) +def test_bind_keycombo(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() mock = MagicMock() @@ -122,4 +123,4 @@ def test_bind_keycombo(viewer_cls: type[TableViewerWidget]): viewer.keymap.register("T")(mock) viewer.keymap.register("T", overwrite=True)(print) - viewer.close() + # viewer.close() From 266f181681c7b4c8994dafad559e55d6a440bb2a Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 24 Sep 2023 23:49:41 +0900 Subject: [PATCH 15/19] reset deleted files --- tests/test_auto_completion.py | 19 ++ tests/test_cell_eval.py | 119 +++++++++++ tests/test_cell_ref_eval.py | 361 ++++++++++++++++++++++++++++++++++ tests/test_colormap.py | 133 +++++++++++++ tests/test_column_dtype.py | 83 ++++++++ tests/test_commands.py | 9 + tests/test_config.py | 5 + tests/test_copy_paste.py | 89 +++++++++ tests/test_core.py | 25 +++ tests/test_dock_widgets.py | 99 ++++++++++ tests/test_finder.py | 114 +++++++++++ tests/test_groupby.py | 50 +++++ tests/test_keyboard_ops.py | 55 ++++++ tests/test_keycombo.py | 189 ++++++++++++++++++ tests/test_magicwidget.py | 28 +++ tests/test_proxy.py | 247 +++++++++++++++++++++++ 16 files changed, 1625 insertions(+) create mode 100644 tests/test_auto_completion.py create mode 100644 tests/test_cell_eval.py create mode 100644 tests/test_cell_ref_eval.py create mode 100644 tests/test_colormap.py create mode 100644 tests/test_column_dtype.py create mode 100644 tests/test_commands.py create mode 100644 tests/test_config.py create mode 100644 tests/test_copy_paste.py create mode 100644 tests/test_core.py create mode 100644 tests/test_dock_widgets.py create mode 100644 tests/test_finder.py create mode 100644 tests/test_groupby.py create mode 100644 tests/test_keyboard_ops.py create mode 100644 tests/test_keycombo.py create mode 100644 tests/test_magicwidget.py create mode 100644 tests/test_proxy.py diff --git a/tests/test_auto_completion.py b/tests/test_auto_completion.py new file mode 100644 index 00000000..01e90725 --- /dev/null +++ b/tests/test_auto_completion.py @@ -0,0 +1,19 @@ +from tabulous import TableViewer + +def test_table_list_completion(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.add_table({}, name="A0") + viewer.add_table({}, name="B0") + viewer.add_table({}, name="X") + assert viewer.tables._ipython_key_completions_() == ["A0", "B0", "X"] + +def test_table_completion(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"x": [1, 2], "yy": [4, 3]}) + assert table._ipython_key_completions_() == ["x", "yy"] + +def test_table_subset_completion(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"x": [1, 2], "y": [4, 3], "z": [0, 0]}) + sub = table.iloc[:, 1:] + assert sub._ipython_key_completions_() == ["y", "z"] diff --git a/tests/test_cell_eval.py b/tests/test_cell_eval.py new file mode 100644 index 00000000..23fb7141 --- /dev/null +++ b/tests/test_cell_eval.py @@ -0,0 +1,119 @@ +from tabulous import TableViewer +import pandas as pd +import pytest + +def test_set_ndarray(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet() + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("=np.arange(10)", (0, 0)) + assert qtable._focused_widget is not None + editor.eval_and_close() + assert qtable._focused_widget is None + for i in range(10): + assert sheet.data.iloc[i, 0] == i + +def test_column_vector_output(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("=np.cumsum(df['a'][0:3])", (2, 1)) + editor.eval_and_close() + assert sheet.data.iloc[0, 1] == 1 + assert sheet.data.iloc[1, 1] == 4 + assert sheet.data.iloc[2, 1] == 9 + + # check evaluation at an existing column works + editor = qtable._create_eval_editor("=np.cumsum(df['a'][0:3])", (1, 1)) + editor.eval_and_close() + assert sheet.data.iloc[0, 1] == 1 + assert sheet.data.iloc[1, 1] == 4 + assert sheet.data.iloc[2, 1] == 9 + +def test_partial_column_vector_output(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5, 7]}) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("=np.cumsum(df['a'][1:3])", (2, 1)) + editor.eval_and_close() + assert pd.isna(sheet.data.iloc[0, 1]) + assert sheet.data.iloc[1, 1] == 3 + assert sheet.data.iloc[2, 1] == 8 + assert pd.isna(sheet.data.iloc[3, 1]) + + # check evaluation at an existing column works + editor = qtable._create_eval_editor("=np.cumsum(df['a'][1:3])", (1, 1)) + editor.eval_and_close() + assert pd.isna(sheet.data.iloc[0, 1]) + assert sheet.data.iloc[1, 1] == 3 + assert sheet.data.iloc[2, 1] == 8 + assert pd.isna(sheet.data.iloc[3, 1]) + +def test_row_vector_output(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5], "b": [2, 4, 6]}) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 1)) + editor.eval_and_close() + assert sheet.data.iloc[4, 0] == 3.0 + assert sheet.data.iloc[4, 1] == 4.0 + + # check evaluation at an existing column works + editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 0)) + editor.eval_and_close() + assert sheet.data.iloc[4, 0] == 3.0 + assert sheet.data.iloc[4, 1] == 4.0 + +def test_partial_row_vector_output(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5], "b": [2, 4, 6], "c": [7, 8, 9]}) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 1)) + editor.eval_and_close() + assert sheet.data.iloc[4, 0] == 3.0 + assert sheet.data.iloc[4, 1] == 4.0 + assert pd.isna(sheet.data.iloc[4, 2]) + + # check evaluation at an existing column works + editor = qtable._create_eval_editor("=np.mean(df.loc[0:2, 'a':'b'], axis=0)", (4, 0)) + editor.eval_and_close() + assert sheet.data.iloc[4, 0] == 3.0 + assert sheet.data.iloc[4, 1] == 4.0 + assert pd.isna(sheet.data.iloc[4, 2]) + +def test_scalar_output(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("=np.mean(df['a'][0:3])", (2, 1)) + editor.eval_and_close() + assert sheet.data.iloc[2, 1] == 3.0 + + # check evaluation at an existing column works + editor = qtable._create_eval_editor("=np.mean(df['a'][0:3]) + 1", (1, 1)) + editor.eval_and_close() + assert sheet.data.iloc[1, 1] == 4.0 + +def test_updating_namespace(make_tabulous_viewer): + import numpy as np + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) + qtable = sheet.native._qtable_view + with pytest.raises(ValueError): + viewer.cell_namespace.update(np=0) + viewer.cell_namespace.update(mean=np.mean) + editor = qtable._create_eval_editor("=mean(df['a'][0:3])", (2, 1)) + editor.eval_and_close() + assert sheet.data.iloc[2, 1] == 3.0 + +def test_updating_namespace_by_decorator(make_tabulous_viewer): + import numpy as np + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 3, 5]}) + qtable = sheet.native._qtable_view + @viewer.cell_namespace.add + def mean(df): + return np.mean(df) + editor = qtable._create_eval_editor("=mean(df['a'][0:3])", (2, 1)) + editor.eval_and_close() + assert sheet.data.iloc[2, 1] == 3.0 diff --git a/tests/test_cell_ref_eval.py b/tests/test_cell_ref_eval.py new file mode 100644 index 00000000..59f9ae3d --- /dev/null +++ b/tests/test_cell_ref_eval.py @@ -0,0 +1,361 @@ +import numpy as np +from tabulous import TableViewer +import pandas as pd +from numpy.testing import assert_allclose, assert_equal +import pytest + +def test_scalar(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(pd.DataFrame({"a": [1, 3, 5]})) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("&=np.mean(df['a'][0:3])", (0, 1)) + assert qtable._focused_widget is not None + editor.eval_and_close() + assert (0, 1) in list(qtable._table_map.keys()) + assert qtable._focused_widget is None + assert sheet.data.iloc[0, 1] == 3.0 + + # changing data triggers re-evaluation + sheet.cell[0, 0] = 4 + assert sheet.data.iloc[0, 1] == 4.0 + sheet.cell[0, 0] = 7 + assert sheet.data.iloc[0, 1] == 5.0 + +def test_delete_ref_by_editing_the_cells(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(pd.DataFrame({"a": [1, 3, 5]})) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("&=np.mean(df['a'][0:3])", (0, 1)) + editor.eval_and_close() + + assert (0, 1) in list(qtable._table_map.keys()) + sheet.cell[0, 2] = "10" + assert (0, 1) in list(qtable._table_map.keys()) + sheet.cell[0, 1] = "10" + assert (0, 1) not in list(qtable._table_map.keys()) + +def test_delete_ref_by_editing_many_cells(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(pd.DataFrame({"a": [1, 3, 5]})) + qtable = sheet.native._qtable_view + editor = qtable._create_eval_editor("&=np.mean(df['a'][0:3])", (0, 1)) + editor.eval_and_close() + + assert (0, 1) in list(qtable._table_map.keys()) + sheet.cell[0:2, 2] = "10" + assert (0, 1) in list(qtable._table_map.keys()) + sheet.cell[0:3, 1] = ["10", "10", "20"] + assert (0, 1) not in list(qtable._table_map.keys()) + +def test_eval_with_no_ref(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet() + sheet.cell[0, 0] = "&=np.arange(5)" + assert len(sheet.cell.ref) == 0 + +def test_1x1_ref_overwritten_by_Nx1_eval(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) + sheet.cell[0, 1] = "&=np.mean(df['a'][0:3])" + assert (0, 1) in sheet.cell.ref + sheet.cell[1, 1] = "&=np.cumsum(df['a'][0:3])" + assert (0, 1) not in sheet.cell.ref + assert (1, 1) in sheet.cell.ref + +def test_eval_undo(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) + sheet.cell[0, 1] = "&=np.mean(df['a'][0:3])" + assert sheet.data.iloc[0, 1] == 2.0 + + sheet.cell[0, 0] = "10" + assert sheet.data.iloc[0, 1] == 5.0 + sheet.undo_manager.undo() + assert sheet.data.iloc[0, 0] == 1 + assert sheet.data.iloc[0, 1] == 2.0 + sheet.undo_manager.redo() + assert sheet.data.iloc[0, 0] == 10 + assert sheet.data.iloc[0, 1] == 5.0 + +def test_eval_undo_with_many_cells(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) + sheet.cell[0, 1] = "&=np.cumsum(df['a'][0:3])" + assert_allclose(sheet.data.iloc[:, 1].values, [1, 3, 6]) + + sheet.cell[0, 0] = "10" + assert_allclose(sheet.data.iloc[:, 1].values, [10, 12, 15]) + sheet.undo_manager.undo() + assert sheet.data.iloc[0, 0] == 1 + assert_allclose(sheet.data.iloc[:, 1].values, [1, 3, 6]) + sheet.undo_manager.redo() + assert sheet.data.iloc[0, 0] == 10 + assert_allclose(sheet.data.iloc[:, 1].values, [10, 12, 15]) + +def test_eval_undo_with_overwrite(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) + sheet.cell[0, 1] = "&=np.mean(df['a'][0:3])" + sheet.cell[1, 1] = "&=np.cumsum(df['a'][0:3])" + assert_allclose(sheet.data.values, [[1, 1], [2, 3], [3, 6]]) + sheet.undo_manager.undo() + assert_allclose(sheet.data.values, [[1, 2], [2, np.nan], [3, np.nan]]) + sheet.undo_manager.undo() + assert_allclose(sheet.data.values, [[1], [2], [3]]) + +@pytest.mark.parametrize( + "expr", + [ + "df['a'][:]", + "df['b'][:]", + "df['a'][:] + df['b'][:]", + "df['a'][:] + df['b'][:].mean()", + "np.cumsum(df['a'][:])", # function that returns a same-length array + "np.mean(df.loc[:, 'a':'b'], axis=1)", # 1D reduction + "df.loc[:, 'a':'b'].mean(axis=1)", # 1D reduction + "np.mean(df.loc[:, 'a':'b'], axis=1) + df['a'][:]", # reduction + array + "df['a'][:].values", # array + ] +) +def test_many_expr(expr: str, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet( + {"a": [1, 2, 3, 4, 5], "b": [5, 4, 3, 4, 5]} + ) + sheet.cell[1, 2] = f"&={expr}" + assert_allclose(sheet.data.iloc[:, 2].values, eval(expr, {"df": sheet.data, "np": np}, {})) + +def test_returns_shorter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet( + {"a": [1, 2, 3, 4, 5], "b": [5, 4, 3, 4, 5]} + ) + sheet.cell[1, 2] = f"&=np.diff(df['a'][:])" + assert_allclose(sheet.data.iloc[:, 2].values, [1, 1, 1, 1, np.nan]) + +def test_called_once(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + count = 0 + @viewer.cell_namespace.add + def func(*_): + nonlocal count + count += 1 + return 0 + + sheet = viewer.add_spreadsheet({"a": [1, 2, 3]}) + sheet.cell[0, 1] = "&=func(df['a'][:])" + assert count == 1 + sheet.cell[0, 0] = "4" + assert count == 2 + +def test_ref_after_insert(make_tabulous_viewer): + # 0 0 0 0 0 + # 0 0 0 X 0 <- edit here + # 0 0 0 0 0 + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((3, 5), dtype=np.float32)) + sheet.cell[1, 3] = "&=np.sum(df.iloc[0:2, 0:1]) + 1" + assert sheet.cell[1, 3] == "1.0" + sheet.columns.insert(2, 1) # insert a column + assert sheet.cell[1, 3] == "0.0" + assert sheet.cell[1, 4] == "1.0" + assert (1, 3) not in sheet.cell.ref + assert (1, 4) in sheet.cell.ref + sheet.undo_manager.undo() + assert sheet.cell[1, 3] == "1.0" + assert sheet.cell[1, 4] == "0.0" + assert (1, 3) in sheet.cell.ref + assert (1, 4) not in sheet.cell.ref + + +def test_ref_after_removal(make_tabulous_viewer): + # 0 0 0 0 0 + # 0 0 0 X 0 <- edit here + # 0 0 0 0 0 + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((3, 5), dtype=np.float32)) + sheet.cell[1, 3] = "&=np.sum(df.iloc[0:2, 0:1]) + 1" + assert sheet.cell[1, 3] == "1.0" + sheet.columns.remove(2, 1) # insert a column + assert sheet.cell[1, 3] == "0.0" + assert sheet.cell[1, 2] == "1.0" + assert (1, 3) not in sheet.cell.ref + assert (1, 2) in sheet.cell.ref + sheet.undo_manager.undo() + assert sheet.cell[1, 3] == "1.0" + assert sheet.cell[1, 2] == "0.0" + assert (1, 3) in sheet.cell.ref + assert (1, 2) not in sheet.cell.ref + + +def test_ref_after_removal_of_column(make_tabulous_viewer): + # --- table --- + # 0 0 0 0 0 + # 0 0 0 X 0 and delete the column including X + # 0 0 0 0 0 + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((3, 5), dtype=np.float32)) + sheet.cell[1, 3] = "&=np.sum(df.iloc[0:2, 0:1]) + 1" + assert sheet.cell[1, 3] == "1.0" + sheet.columns.remove(3, 1) # remove a column + assert sheet.cell[1, 3] == "0.0" + assert len(sheet.cell.ref) == 0 + sheet.undo_manager.undo() + assert sheet.cell[1, 3] == "1.0" + assert sheet.cell[1, 2] == "0.0" + assert (1, 3) in sheet.cell.ref + assert (1, 2) not in sheet.cell.ref + + +def test_removing_source(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((3, 1), dtype=np.float32)) + sheet.cell[0, 1] = "&=np.sum(df.iloc[:, 0])" + assert len(sheet.cell.ref) == 1 + sheet.columns.remove(0, 1) # remove a column + assert sheet.data.shape == (3, 1), "wrong shape" + assert len(sheet.cell.ref) == 0, "slot not removed" + + +def test_removing_one_of_two_sources(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((3, 2), dtype=np.float32)) + sheet.cell[0, 2] = "&=np.sum(df.iloc[:, 0]) + np.sum(df.iloc[:, 1])" + assert len(sheet.cell.ref) == 1 + sheet.columns.remove(0, 1) # remove a column + assert sheet.data.shape == (3, 2), "wrong shape" + assert len(sheet.cell.ref) == 0, "slot not removed" + +def test_removing_or_inserting_left_column(make_tabulous_viewer): + # --- table --- + # 0 0 &=np.sum(df.iloc[:, 1:2]) + # 0 0 + # 0 0 + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet([[0., 1.], [1., 2.], [3., 5.]]) + sheet.cell[0, 2] = "&=np.sum(df.iloc[:, 1])" + assert sheet.cell[0, 2] == "8.0" + sheet.columns.remove(0, 1) # remove a column + assert sheet.data.shape == (3, 2) + assert sheet.cell[0, 1] == "8.0" + sheet.cell[1, 0] = 4 + assert sheet.cell[0, 1] == "10.0" + sheet.columns.insert(0, 1) # insert a column + assert sheet.data.shape == (3, 3) + assert sheet.cell[0, 2] == "10.0" + sheet.cell[1, 0] = 5 + assert sheet.cell[0, 2] == "10.0" + +def test_eval_during_sort(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + rng = np.random.default_rng(123) + sheet = viewer.add_spreadsheet(rng.poisson(3, size=(5, 1))) + sheet.proxy.set([1, 3, 2, 4, 0]) + sheet.cell[1, 1] = "&=np.sum(df.iloc[:, 0])" + assert sheet.data_shown.iloc[1, 1] == np.sum(sheet.data.iloc[:, 0]) + sheet.proxy.reset() + assert sheet.data_shown.iloc[3, 1] == np.sum(sheet.data.iloc[:, 0]) + assert (3, 1) in sheet.cell.ref + +def test_eval_during_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + rng = np.random.default_rng(123) + sheet = viewer.add_spreadsheet({"a": [0, 1, 1, 0, 1], "b": rng.poisson(3, size=5)}) + sheet.proxy.filter("a == 1") + sheet.cell[0, 2] = "&=np.sum(df.iloc[:, 1])" + assert sheet.data_shown.iloc[0, 2] == np.sum(sheet.data.iloc[:, 1]) + sheet.proxy.reset() + assert sheet.data_shown.iloc[1, 2] == np.sum(sheet.data.iloc[:, 1]) + assert (1, 2) in sheet.cell.ref + +def test_broadcasting_during_sort(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + rng = np.random.default_rng(123) + sheet = viewer.add_spreadsheet(rng.poisson(3, size=(5, 1))) + order = [1, 3, 2, 4, 0] + sheet.proxy.set(order) + sheet.cell[1, 1] = "&=np.cumsum(df.iloc[:, 0])" + assert_equal(sheet.data_shown.iloc[:, 1].values, np.cumsum(sheet.data.iloc[:, 0])[order].values) + sheet.proxy.reset() + assert_equal(sheet.data_shown.iloc[:, 1].values, np.cumsum(sheet.data.iloc[:, 0]).values) + assert (3, 1) in sheet.cell.ref + +def test_broadcasting_during_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + rng = np.random.default_rng(123) + sheet = viewer.add_spreadsheet({"a": [0, 1, 1, 0, 1], "b": rng.poisson(3, size=5)}) + sheet.proxy.filter("a == 1") + idx = sheet.proxy.as_indexer() + sheet.cell[0, 2] = "&=np.cumsum(df.iloc[:, 1])" + assert_equal(sheet.data_shown.iloc[:, 2].values, np.cumsum(sheet.data.iloc[:, 1])[idx].values) + sheet.proxy.reset() + assert_equal(sheet.data_shown.iloc[:, 2].values, np.cumsum(sheet.data.iloc[:, 1]).values) + assert (1, 2) in sheet.cell.ref + +def _assert_status_equal(s: str, ref: str): + l = len("") + r = len("") + assert s[l:-r] == ref + +def test_status_tip(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((3, 2))) + sheet.cell[0, 1] = "&=np.sum(df.iloc[:, 0])" + sheet.cell[0, 2] = "&=np.sin(df.iloc[:, 0])" + sheet.move_iloc(0, 0) + _assert_status_equal(viewer.status, "") + sheet.move_iloc(0, 1) + _assert_status_equal(viewer.status, "df.iloc[0:1, 1:2] = np.sum(df.iloc[:, 0])") + sheet.move_iloc(1, 1) + _assert_status_equal(viewer.status, "") + sheet.move_iloc(0, 2) + _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") + sheet.move_iloc(1, 2) + _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") + sheet.move_iloc(2, 2) + _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") + +def test_status_tip_with_proxy(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet(np.zeros((5, 2))) + sheet.cell[1, 1] = "&=np.sum(df.iloc[:, 0])" + sheet.cell[0, 2] = "&=np.sin(df.iloc[:, 0])" + sheet.proxy.set([False, True, True, False, True]) + sheet.move_iloc(0, 0) + _assert_status_equal(viewer.status, "") + sheet.move_iloc(0, 1) + _assert_status_equal(viewer.status, "df.iloc[1:2, 1:2] = np.sum(df.iloc[:, 0])") + sheet.move_iloc(1, 1) + _assert_status_equal(viewer.status, "") + sheet.move_iloc(0, 2) + _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") + sheet.move_iloc(1, 2) + _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") + sheet.move_iloc(2, 2) + _assert_status_equal(viewer.status, "df.iloc[:, 2:3] = np.sin(df.iloc[:, 0])") + +def test_called_when_expanded(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + # check scalar output + sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]]) + sheet.cell[1, 2] = "&=np.sum(df.iloc[:, 1])" + assert sheet.data.iloc[1, 2] == 6 + sheet.cell[3, 1] = "4" + assert sheet.data.iloc[1, 2] == 10 + + # check vector output + sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]]) + sheet.cell[1, 2] = "&=np.cumsum(df.iloc[:, 1])" + assert_equal(sheet.data.iloc[:, 2].values, [1, 3, 6]) + sheet.cell[3, 1] = "4" + assert_equal(sheet.data.iloc[:, 2].values, [1, 3, 6, 10]) + +def test_N(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]]) + sheet.cell[1, 2] = "&=np.sum(df.iloc[:, 1])/N" + assert sheet.data.iloc[1, 2] == 2 + sheet.cell[3, 1] = "4" + assert sheet.data.iloc[1, 2] == 2.5 + sheet.cell[1, 3] = "&=np.zeros(N)" + assert sheet.data.iloc[1, 3] == 0 diff --git a/tests/test_colormap.py b/tests/test_colormap.py new file mode 100644 index 00000000..42182fc8 --- /dev/null +++ b/tests/test_colormap.py @@ -0,0 +1,133 @@ +import pandas as pd +from tabulous.widgets import Table +from tabulous.color import normalize_color +import numpy as np + +cmap = { + "a": (255, 0, 0, 255), + "b": (0, 255, 0, 255), + "c": (0, 0, 255, 255), +} + +def _cmap_func(x): + return cmap[x] + +def test_foreground(): + table = Table({"char": ["a", "b", "c"]}) + default_color = table.cell.text_color[0, 0] + + table.text_color.set("char", cmap) + assert table.cell.text_color[0, 0] == normalize_color(cmap["a"]) + assert table.cell.text_color[1, 0] == normalize_color(cmap["b"]) + assert table.cell.text_color[2, 0] == normalize_color(cmap["c"]) + + table.text_color.set("char", None) + assert table.cell.text_color[0, 0] == default_color + assert table.cell.text_color[1, 0] == default_color + assert table.cell.text_color[2, 0] == default_color + + table.text_color.set("char", _cmap_func) + assert table.cell.text_color[0, 0] == normalize_color(cmap["a"]) + assert table.cell.text_color[1, 0] == normalize_color(cmap["b"]) + assert table.cell.text_color[2, 0] == normalize_color(cmap["c"]) + + +def test_background(): + table = Table({"char": ["a", "b", "c"]}) + default_color = table.cell.background_color[0, 0] + + table.background_color.set("char", cmap) + assert table.cell.background_color[0, 0] == normalize_color(cmap["a"]) + assert table.cell.background_color[1, 0] == normalize_color(cmap["b"]) + assert table.cell.background_color[2, 0] == normalize_color(cmap["c"]) + + table.background_color.set("char", None) + assert table.cell.background_color[0, 0] == default_color + assert table.cell.background_color[1, 0] == default_color + assert table.cell.background_color[2, 0] == default_color + + table.background_color.set("char", _cmap_func) + assert table.cell.background_color[0, 0] == normalize_color(cmap["a"]) + assert table.cell.background_color[1, 0] == normalize_color(cmap["b"]) + assert table.cell.background_color[2, 0] == normalize_color(cmap["c"]) + + +def test_linear_interpolation(): + table = Table( + { + "A": np.arange(10), + "B": np.arange(10) > 5, + "C": pd.date_range("2020-01-01", periods=10), + } + ) + table.text_color.set("A", interp_from=["red", "blue"]) + table.text_color.set("B", interp_from=["red", "blue"]) + table.text_color.set("C", interp_from=["red", "blue"]) + assert table.cell.text_color[0, 0] == normalize_color("red") + assert table.cell.text_color[4, 0] == normalize_color((141, 0, 113, 255)) + assert table.cell.text_color[9, 0] == normalize_color("blue") + assert table.cell.text_color[0, 1] == normalize_color("red") + assert table.cell.text_color[9, 1] == normalize_color("blue") + assert table.cell.text_color[0, 2] == normalize_color("red") + assert table.cell.text_color[4, 2] == normalize_color((141, 0, 113, 255)) + assert table.cell.text_color[9, 2] == normalize_color("blue") + +def test_linear_segmented(): + table = Table( + { + "A": np.arange(-3, 4), + "C": pd.date_range("2020-01-01", periods=7), + } + ) + table.text_color.set("A", interp_from=["red", "gray", "blue"]) + table.text_color.set("C", interp_from=["red", "gray", "blue"]) + assert table.cell.text_color[0, 0] == normalize_color("red") + assert table.cell.text_color[3, 0] == normalize_color("gray") + assert table.cell.text_color[6, 0] == normalize_color("blue") + assert table.cell.text_color[0, 1] == normalize_color("red") + assert table.cell.text_color[3, 1] == normalize_color("gray") + assert table.cell.text_color[6, 1] == normalize_color("blue") + + +def test_invert(): + table = Table({"A": np.arange(10)}) + table.text_color.set("A", interp_from=["red", "blue"]) + red = normalize_color("red") + red_inv = tuple(255 - x for x in red[:3]) + (red[3],) + + assert table.cell.text_color[0, 0] == red + table.text_color.invert("A") + assert table.cell.text_color[0, 0] == red_inv + +def test_set_opacity(): + table = Table({"A": np.arange(10)}) + table.text_color.set("A", interp_from=["red", "blue"]) + assert table.cell.text_color[0, 0][3] == 255 + + table.text_color.set_opacity("A", 0.5) + assert table.cell.text_color[0, 0][3] == 127 + + table.text_color.set("A", interp_from=["red", "blue"], opacity=0.5) + assert table.cell.text_color[0, 0][3] == 127 + +def test_adjust_brightness(): + table = Table({"A": np.arange(10)}) + table.text_color.set("A", interp_from=["red", "blue"]) + assert table.cell.text_color[0, 0] == normalize_color("red") + assert table.cell.text_color[9, 0] == normalize_color("blue") + + table.text_color.adjust_brightness("A", 0.5) + assert table.cell.text_color[0, 0] > normalize_color("red") + assert table.cell.text_color[9, 0] > normalize_color("blue") + + table.text_color.adjust_brightness("A", -0.5) + assert table.cell.text_color[0, 0] == normalize_color("red") + assert table.cell.text_color[9, 0] == normalize_color("blue") + + table.text_color.adjust_brightness("A", -0.5) + assert table.cell.text_color[0, 0] < normalize_color("red") + assert table.cell.text_color[9, 0] < normalize_color("blue") + + table.text_color.adjust_brightness("A", 0.5) + assert table.cell.text_color[0, 0] == normalize_color("red") + assert table.cell.text_color[9, 0] == normalize_color("blue") diff --git a/tests/test_column_dtype.py b/tests/test_column_dtype.py new file mode 100644 index 00000000..68849a99 --- /dev/null +++ b/tests/test_column_dtype.py @@ -0,0 +1,83 @@ +from tabulous import TableViewer +import numpy as np +import pandas as pd +import pytest +from typing import Callable + +def test_setting_column_dtype(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"number": [1, 2, 3], "char": ["a", "b", "a"]}) + assert sheet.dtypes == {} + + sheet.dtypes["number"] = "float32" + sheet.dtypes["char"] = "category" + assert sheet.dtypes == {"number": "float32", "char": "category"} + + df = sheet.data + assert df.dtypes.iloc[0] == "float32" + assert df.dtypes.iloc[1] == "category" + + sheet.undo_manager.undo() + assert sheet.dtypes == {"number": "float32"} + + sheet.undo_manager.undo() + assert sheet.dtypes == {} + +def test_datetime(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"datetime": ["2020-01-01", "2020-01-02", "2020-01-03"]}) + assert sheet.data.dtypes.iloc[0] == "object" + + sheet.dtypes["datetime"] = "datetime64[ns]" + assert sheet.data.dtypes.iloc[0] == "datetime64[ns]" + +def test_timedelta(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"timedelta": ["1d", "2d", "3d"]}) + assert sheet.data.dtypes.iloc[0] == "object" + + sheet.dtypes["timedelta"] = "timedelta64[ns]" + assert sheet.data.dtypes.iloc[0] == "timedelta64[ns]" + +def test_updating_column_name(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"number": [1, 2, 3], "char": ["a", "b", "a"]}) + + sheet.dtypes["number"] = "float32" + sheet.dtypes["char"] = "category" + + assert sheet.dtypes == {"number": "float32", "char": "category"} + + sheet.columns[0] = "new_name" + + assert sheet.dtypes == {"new_name": "float32", "char": "category"} + +def test_deleting_column_name(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"number": [1, 2, 3], "char": ["a", "b", "a"]}) + + sheet.dtypes["number"] = "float32" + sheet.dtypes["char"] = "category" + + assert sheet.dtypes == {"number": "float32", "char": "category"} + + sheet.columns.remove(0) + + assert sheet.dtypes == {"char": "category"} + +@pytest.mark.parametrize( + "dtype, fn", + [("int", lambda n: pd.Series(np.arange(n), dtype=np.int32)), + ("float", lambda n: pd.Series(np.arange(n), dtype=np.float64)), + ("datetime64[ns]", lambda n: pd.date_range("2020/01/01", periods=n, freq="2D")), + ("timedelta64[ns]", lambda n: pd.timedelta_range("00:00:00", periods=n, freq="60s")), + ("interval", lambda n: pd.interval_range(0, periods=n, freq=1)), + ], +) +def test_can_parse(dtype, fn: Callable[[int], pd.Series], make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + data = fn(5) + sheet = viewer.add_spreadsheet({"c": data}) + sheet.dtypes["c"] = dtype + assert sheet.dtypes["c"] == dtype + assert sheet.data["c"].dtype == dtype diff --git a/tests/test_commands.py b/tests/test_commands.py new file mode 100644 index 00000000..9f76bb4f --- /dev/null +++ b/tests/test_commands.py @@ -0,0 +1,9 @@ +from tabulous import TableViewer, commands as cmds +import numpy as np + +def test_groupby(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({"a": np.arange(20), "b": np.arange(20) // 5}) + sheet.columns.selected = 1 + cmds.column.run_groupby(viewer) + assert len(viewer.tables) == 2 diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 00000000..bc612719 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,5 @@ +from tabulous import TableViewer + +def test_config_works(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + viewer.config diff --git a/tests/test_copy_paste.py b/tests/test_copy_paste.py new file mode 100644 index 00000000..d358deb3 --- /dev/null +++ b/tests/test_copy_paste.py @@ -0,0 +1,89 @@ +from tabulous import TableViewer +import numpy as np +from numpy import testing + +def assert_equal(a, b): + return testing.assert_equal(np.asarray(a), np.asarray(b)) + +def test_copy_and_paste_1x1(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({ + "a": [0, 1, 2, 3, 4], + "b": [2, 4, 6, 8, 10], + "c": [-1, -1, -1, -1, -1], + }, editable=True) + sl_src = (2, 2) + sl_dst = (1, 1) + viewer.copy_data([sl_src]) # copy -1 + old_value = table.data.iloc[sl_dst] + copied = table.data.iloc[sl_src] + viewer.paste_data([sl_dst]) # paste -1 + assert table.data.iloc[sl_dst] == copied + + table.undo_manager.undo() + assert table.data.iloc[sl_dst] == old_value + + +def test_copy_and_paste_same_shape(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({ + "a": [0, 1, 2, 3, 4], + "b": [2, 4, 6, 8, 10], + "c": [-1, -1, -1, -1, -1], + }, editable=True) + + sl_src = (slice(3, 5), slice(1, 3)) + sl_dst = (slice(2, 4), slice(0, 2)) + viewer.copy_data([sl_src]) + old_value = table.data.iloc[sl_dst].copy() + copied = table.data.iloc[sl_src].copy() + viewer.paste_data([sl_dst]) + assert_equal(table.data.iloc[sl_dst], copied) + + table.undo_manager.undo() + assert_equal(table.data.iloc[sl_dst], old_value) + + +def test_copy_array_and_paste_single(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({ + "a": [0, 1, 2, 3, 4], + "b": [2, 4, 6, 8, 10], + "c": [-1, -1, -1, -1, -1], + }, editable=True) + + sl_src = (slice(3, 5), slice(1, 3)) + sl_dst = (slice(2, 4), slice(0, 2)) + viewer.copy_data([sl_src]) + old_value = table.data.iloc[sl_dst].copy() + copied = table.data.iloc[sl_src].copy() + viewer.paste_data([(2, 0)]) # paste with single cell selection + assert_equal(table.data.iloc[sl_dst], copied) + + table.undo_manager.undo() + assert_equal(table.data.iloc[sl_dst], old_value) + +def test_paste_with_column_selected(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_spreadsheet({ + "a": [0, 1, 2, 3, 4], + "b": [2, 4, 6, 8, 10], + "c": [-1, -1, -1, -1, -1], + }) + + viewer.copy_data([(slice(0, 5), slice(2, 3))]) + table._qwidget._qtable_view._selection_model.move_to(-1, 0) + viewer.paste_data() + assert_equal(table.data.iloc[:, 0], np.full(5, -1)) + +def test_paste_with_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + sheet = viewer.add_spreadsheet({ + "a": [0, 1, 2, 3, 4], + "b": ["a", "b", "c", "d", "e"], + "c": ["x"] * 5, + }) + viewer.copy_data([(slice(0, 3), slice(2, 3))]) + sheet.proxy.filter("a % 2 == 0") + viewer.paste_data([(slice(0, 3), slice(1, 2))]) + assert_equal(sheet.data.iloc[:, 1], ["x", "b", "x", "d", "x"]) diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 00000000..73b712c0 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,25 @@ +import tabulous as tbl +import pandas as pd +from pathlib import Path +import pytest +from glob import glob +import runpy +import warnings + +DATA_PATH = Path(__file__).parent / "data" + +def test_view(): + df = pd.read_csv(DATA_PATH / "test.csv") + tbl.view_table(df).close() + tbl.view_spreadsheet(df).close() + +def test_io(): + tbl.read_csv(DATA_PATH / "test.csv").close() + +@pytest.mark.parametrize( + "fname", [f for f in glob("examples/*.py") if "napari" not in f and "seaborn" not in f] +) +def test_examples(fname): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + runpy.run_path(fname) diff --git a/tests/test_dock_widgets.py b/tests/test_dock_widgets.py new file mode 100644 index 00000000..2410f99a --- /dev/null +++ b/tests/test_dock_widgets.py @@ -0,0 +1,99 @@ +from magicgui import magicgui +from tabulous import TableViewer +from magicgui.widgets import PushButton + +def dataframe_equal(a, b): + """Check two DataFrame (or tuple of DataFrames) are equal.""" + if isinstance(a, tuple): + if a == (): + return a == b + return all(dataframe_equal(a0, b0) for a0, b0 in zip(a, b)) + return a.equals(b) + +def test_add_and_remove(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + name = "NAME" + btn = PushButton(text="test_button", name=name) + dock = viewer.add_dock_widget(btn) + assert dock.windowTitle() == name + assert name in list(viewer._dock_widgets.keys()) + viewer.remove_dock_widget(name) + assert name not in list(viewer._dock_widgets.keys()) + +def test_table_choice(make_tabulous_viewer): + from tabulous.types import TableData + viewer: TableViewer = make_tabulous_viewer() + + @magicgui + def f(df: TableData): + pass + + viewer.add_dock_widget(f) + + assert f["df"].choices == () + + table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") + + assert dataframe_equal(f["df"].choices, (table0.data,)) + value = f["df"].value + assert all(value["a"] == [0, 0]) + assert all(value["b"] == [1, 1]) + + table1 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-1") + assert dataframe_equal(f["df"].choices, (table0.data, table1.data)) + viewer.tables.pop() + assert dataframe_equal(f["df"].choices, (table0.data,)) + viewer.tables.pop() + assert dataframe_equal(f["df"].choices, ()) + +def test_layer_update(make_tabulous_viewer): + from tabulous.types import TableData + viewer: TableViewer = make_tabulous_viewer() + + @magicgui + def f(df: TableData) -> TableData: + return df + + viewer.add_dock_widget(f) + + table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") + + f.call_button.clicked() + assert len(viewer.tables) == 2 + result = viewer.tables[-1] + assert dataframe_equal(result.data, table0.data) + + # second click will not add a new layer + f.call_button.clicked() + assert len(viewer.tables) == 2 + +def test_table_column_choice(make_tabulous_viewer): + from tabulous.types import TableColumn + + viewer: TableViewer = make_tabulous_viewer() + + @magicgui + def f(df: TableColumn): + pass + + viewer.add_dock_widget(f) + + assert f["df"]._dataframe_choices.choices == () + assert f["df"]._column_choices.choices == () + + table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") + + assert len(f["df"]._dataframe_choices.choices) == 1 + assert len(f["df"]._column_choices.choices) == 2 + assert all(f["df"].value == [0, 0]) + + table1 = viewer.add_table({"a": [0, 0, 1]}, name="table-1") + assert len(f["df"]._dataframe_choices.choices) == 2 + assert len(f["df"]._column_choices.choices) == 2 + + assert dataframe_equal(f["df"]._dataframe_choices.value, table0.data) + + del viewer.tables["table-0"] + + assert len(f["df"]._column_choices.choices) == 1 + assert all(f["df"].value == [0, 0, 1]) diff --git a/tests/test_finder.py b/tests/test_finder.py new file mode 100644 index 00000000..a2f9ef36 --- /dev/null +++ b/tests/test_finder.py @@ -0,0 +1,114 @@ +from tabulous import TableViewer +from tabulous import commands as cmds +import pandas as pd +from ._utils import selection_equal + +def test_find_by_value(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + layer = viewer.add_table( + pd.DataFrame({'a': [1, 2, 3], 'b': [2, 3, 2], 'c': ["a", "2", "2"]}) + ) + finder = cmds.table.show_finder_widget(viewer) + finder.searchBox().setText("2") + + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findNext() + selection_equal(layer.selections, [(0, 1)]) + finder.findNext() + selection_equal(layer.selections, [(2, 1)]) + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findPrevious() + selection_equal(layer.selections, [(2, 1)]) + finder.findPrevious() + selection_equal(layer.selections, [(0, 1)]) + +def test_find_by_text(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + layer = viewer.add_spreadsheet( + pd.DataFrame({'a': ["aa", "bb", "cc"], 'b': ["bc", "cc", "cc"]}) + ) + finder = cmds.table.show_finder_widget(viewer) + finder.searchBox().setText("cc") + + finder.findNext() + selection_equal(layer.selections, [(2, 0)]) + finder.findNext() + selection_equal(layer.selections, [(1, 1)]) + finder.findNext() + selection_equal(layer.selections, [(2, 1)]) + finder.findNext() + selection_equal(layer.selections, [(2, 0)]) + finder.findPrevious() + selection_equal(layer.selections, [(2, 1)]) + finder.findPrevious() + selection_equal(layer.selections, [(1, 1)]) + +def test_find_by_partial_text(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + layer = viewer.add_spreadsheet( + pd.DataFrame({'a': ["aa", "bb", "cc"], 'b': ["bc", "cc", "cb"]}) + ) + finder = cmds.table.show_finder_widget(viewer) + finder.searchBox().setText("b") + finder.cbox_match.setCurrentIndex(2) # partial match + + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findNext() + selection_equal(layer.selections, [(0, 1)]) + finder.findNext() + selection_equal(layer.selections, [(2, 1)]) + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findPrevious() + selection_equal(layer.selections, [(2, 1)]) + finder.findPrevious() + selection_equal(layer.selections, [(0, 1)]) + + +def test_find_by_regex(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + layer = viewer.add_spreadsheet( + pd.DataFrame({'a': ["a123a", "b321b", "c2h2c"], 'b': ["a442a", "1cc2", "a12a"]}) + ) + finder = cmds.table.show_finder_widget(viewer) + finder.searchBox().setText(r"a\d+a") + finder.cbox_match.setCurrentIndex(3) # regex + + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findNext() + selection_equal(layer.selections, [(0, 1)]) + finder.findNext() + selection_equal(layer.selections, [(2, 1)]) + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findPrevious() + selection_equal(layer.selections, [(2, 1)]) + finder.findPrevious() + selection_equal(layer.selections, [(0, 1)]) + + +def test_find_by_eval(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + layer = viewer.add_spreadsheet( + pd.DataFrame({'a': ["0.13", "a", "2.5"], 'b': ["0.32", "-1.2", "0.54"]}) + ) + finder = cmds.table.show_finder_widget(viewer) + finder.searchBox().setText("0 < float(x) < 1") + finder.cbox_match.setCurrentIndex(4) # eval + + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findNext() + selection_equal(layer.selections, [(0, 1)]) + finder.findNext() + selection_equal(layer.selections, [(2, 1)]) + finder.findNext() + selection_equal(layer.selections, [(1, 0)]) + finder.findPrevious() + selection_equal(layer.selections, [(2, 1)]) + finder.findPrevious() + selection_equal(layer.selections, [(0, 1)]) diff --git a/tests/test_groupby.py b/tests/test_groupby.py new file mode 100644 index 00000000..40a5453c --- /dev/null +++ b/tests/test_groupby.py @@ -0,0 +1,50 @@ +from tabulous import TableViewer +import pandas as pd +from pandas.testing import assert_frame_equal + +# group by single column +df0 = pd.DataFrame({ + "a": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "b": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "c": ["A", "B", "A", "A", "B", "A", "A", "B", "A", "B"], +}) + +# group by two columns +df1 = pd.DataFrame({ + "a": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "b": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "c": ["A", "B", "A", "A", "B", "A", "A", "B", "A", "B"], + "d": ["X", "X", "Y", "Y", "X", "X", "Y", "Y", "Y", "Y"], +}) + +def test_add_groupby_single(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + grouped = df0.groupby("c") + table = viewer.add_groupby(grouped, name="test") + each_df = [x[1] for x in grouped] + assert table.current_group == "A" + assert_frame_equal(each_df[0], table._qwidget.dataShown()) + table.current_group = "B" + assert table.current_group == "B" + assert_frame_equal(each_df[1], table._qwidget.dataShown()) + +def test_add_groupby_double(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_groupby(df1.groupby(["c", "d"])) + assert table.current_group == ("A", "X") + +def test_add_list(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_groupby( + [{"C0": [0, 0], "C1": [1, 1]}, + {"C0": [5, 0], "C1": [6, 1]},] + ) + assert table.current_group == 0 + +def test_add_dict(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_groupby( + {"a": {"C0": [0, 0], "C1": [1, 1]}, + "b": {"C0": [5, 0], "C1": [6, 1]},} + ) + assert table.current_group == "a" diff --git a/tests/test_keyboard_ops.py b/tests/test_keyboard_ops.py new file mode 100644 index 00000000..7156a331 --- /dev/null +++ b/tests/test_keyboard_ops.py @@ -0,0 +1,55 @@ +import sys +import pytest +from tabulous import TableViewer +import numpy as np +from pytestqt.qtbot import QtBot +from qtpy import QtWidgets as QtW +from qtpy.QtCore import Qt + +def test_add_spreadsheet_and_move(qtbot: QtBot): + viewer = TableViewer() + qtbot.addWidget(viewer._qwidget) + qtbot.keyClick(viewer._qwidget, Qt.Key.Key_N, Qt.KeyboardModifier.ControlModifier) + assert len(viewer.tables) == 1 + sheet = viewer.current_table + qtbot.keyClick(sheet._qwidget._qtable_view, Qt.Key.Key_0) + qtbot.keyClick(sheet._qwidget, Qt.Key.Key_Down) + assert sheet.data.shape == (1, 1) + assert sheet.cell[0, 0] == "0" + +def test_movements_in_popup(qtbot: QtBot): + if sys.platform == "darwin": + pytest.skip("Skipping test on macOS because it has a different focus policy.") + viewer = TableViewer() + qtbot.addWidget(viewer._qwidget) + sheet = viewer.add_spreadsheet(np.zeros((10, 10))) + + sheet.view_mode = "popup" + popup = QtW.QApplication.focusWidget() + qtbot.keyClick(popup, Qt.Key.Key_Down) + qtbot.keyClick(popup, Qt.Key.Key_1) + qtbot.keyClick(popup, Qt.Key.Key_Down) + qtbot.keyClick(popup, Qt.Key.Key_Escape) + + assert popup is not QtW.QApplication.focusWidget() + assert sheet.view_mode == "normal" + assert sheet.cell[1, 0] == "1" + +@pytest.mark.parametrize( + "mode, key", + [("popup", Qt.Key.Key_P), ("vertical", Qt.Key.Key_V), ("horizontal", Qt.Key.Key_H)] +) +def test_changing_view_mode(qtbot: QtBot, mode, key): + viewer = TableViewer() + qtbot.addWidget(viewer._qwidget) + sheet = viewer.add_spreadsheet(np.zeros((10, 10))) + + assert sheet.view_mode == "normal" + qtbot.keyClick(viewer._qwidget, Qt.Key.Key_K, Qt.KeyboardModifier.ControlModifier) + qtbot.keyClick(viewer._qwidget, key) + + assert sheet.view_mode == mode + + qtbot.keyClick(viewer._qwidget, Qt.Key.Key_K, Qt.KeyboardModifier.ControlModifier) + qtbot.keyClick(viewer._qwidget, Qt.Key.Key_N) + assert sheet.view_mode == "normal" diff --git a/tests/test_keycombo.py b/tests/test_keycombo.py new file mode 100644 index 00000000..7a253232 --- /dev/null +++ b/tests/test_keycombo.py @@ -0,0 +1,189 @@ +from tabulous._keymap import QtKeyMap +from unittest.mock import MagicMock +import pytest + +@pytest.mark.parametrize( + "key", + ["Ctrl+C", ["Ctrl+C", "C"], ["Ctrl+C", "Shift+Z", "Alt+O"], "Ctrl+C, Shift+Z, Alt+O"], +) +def test_keypress(key): + mock = MagicMock() + + keymap = QtKeyMap() + keymap.bind(key, mock) + + mock.assert_not_called() + keymap.press_key("Ctrl+@") + mock.assert_not_called() + keymap.press_key(key) + mock.assert_called_once() + + +def test_keycombo_initialization(): + mock = MagicMock() + + keymap = QtKeyMap() + keymap.bind(["A", "B", "C"], mock) + + mock.assert_not_called() + keymap.press_key("A") + mock.assert_not_called() + keymap.press_key("B") + mock.assert_not_called() + keymap.press_key("C") + mock.assert_called_once() + mock.reset_mock() + keymap.press_key("C") # combo initialized + mock.assert_not_called() + keymap.press_key("A") + keymap.press_key("B") + keymap.press_key("B") + keymap.press_key("C") + mock.assert_not_called() + keymap.press_key("A") + keymap.press_key("B") + keymap.press_key("C") + mock.assert_called_once() + + +def test_activated_callback(): + keymap = QtKeyMap() + mock = MagicMock() + + keymap.bind(["Ctrl+C", "Ctrl+V"], lambda: 0) + keymap.bind("Ctrl+C", mock) + keymap.press_key("Ctrl+C") + mock.assert_called_once() + +def test_activate_modifier_only(): + keymap = QtKeyMap() + mock1 = MagicMock() + mock2 = MagicMock() + + keymap.bind(["Alt"], mock1) + keymap.bind(["Alt", "A"], mock2) + + keymap.press_key("Alt") + mock1.assert_called_once() + mock1.reset_mock() + + keymap.press_key("A") + mock2.assert_called_once() + + keymap.press_key("Alt") + mock1.assert_called_once() + mock1.reset_mock() + + # BUG: this is not working + # keymap.press_key("Alt") + # mock1.assert_not_called() + + # keymap.press_key("Alt") + # mock1.assert_called_once() + +# def test_combo_with_conflicted_modifier(): +# """In Qt, Alt is activated before Alt+A is activated.""" + +# keymap = QtKeyMap() +# mock1 = MagicMock() +# mock2 = MagicMock() + +# keymap.bind(["Alt"], mock1) +# keymap.bind(["Alt", "A"], mock2) + +# keymap.press_key("Alt") +# mock1.assert_called_once() + +# keymap.press_key("Alt+A") +# mock2.assert_called_once() + +def test_combo_with_different_modifiers(): + keymap = QtKeyMap() + mock = MagicMock() + + keymap.bind("Ctrl+K, Shift+A", mock) + + keymap.press_key("Ctrl+K") + keymap.press_key("Shift+A") + mock.assert_called_once() + mock.reset_mock() + + keymap.press_key("Shift+A") + mock.assert_not_called() + +def test_deactivated_callback(): + keymap = QtKeyMap() + mock = MagicMock() + + keymap.bind(["Ctrl+C", "Ctrl+V"], lambda: 0) + keymap.bind_deactivated("Ctrl+C", mock) + keymap.press_key("Ctrl+C") + mock.assert_not_called() + keymap.press_key("Ctrl+V") + mock.assert_called_once() + +def test_callback_to_child_map(): + keymap = QtKeyMap() + mock = MagicMock() + + func0 = lambda: mock(0) + func1 = lambda: mock(1) + keymap.bind("Ctrl+C", func0) + keymap.bind(["Ctrl+C", "Ctrl+V"], func1) + keymap.press_key("Ctrl+C") + mock.assert_called_once() + mock.assert_called_with(0) + keymap.press_key("Ctrl+V") + mock.assert_called_with(1) + +@pytest.mark.parametrize("key0", ["Ctrl+C", "Ctrl+K, Ctrl+C"]) +@pytest.mark.parametrize("key1", ["Ctrl+A", "Ctrl+K, Ctrl+A"]) +def test_rebind(key0, key1): + keymap = QtKeyMap() + mock = MagicMock() + + keymap.bind(key0, mock) + keymap.rebind(key0, key1) + + keymap.press_key(key0) + mock.assert_not_called() + + keymap.press_key(key1) + mock.assert_called_once() + +def test_parametric(): + keymap = QtKeyMap() + mock = MagicMock() + + keymap.bind("Ctrl+{}", mock) + keymap.bind("Ctrl+A", lambda: None) + keymap.bind("Ctrl+B, Ctrl+C", lambda: None) + + mock.assert_not_called() + keymap.press_key("Ctrl+A") + mock.assert_not_called() + keymap.press_key("Ctrl+2") + mock.assert_called_with("2") + keymap.press_key("Ctrl+Z") + mock.assert_called_with("Z") + mock.reset_mock() + keymap.press_key("Ctrl+Shift+1") + mock.assert_not_called() + keymap.press_key("Ctrl") + mock.assert_not_called() + keymap.press_key("Ctrl+T") + mock.assert_called_with("T") + mock.reset_mock() + keymap.press_key("Ctrl+B") + mock.assert_not_called() + +def test_parametric_combo(): + keymap = QtKeyMap() + mock = MagicMock() + + keymap.bind("Ctrl+B, Alt+{}", mock) + + keymap.press_key("Ctrl+B") + mock.assert_not_called() + keymap.press_key("Alt+A") + mock.assert_called_with("A") diff --git a/tests/test_magicwidget.py b/tests/test_magicwidget.py new file mode 100644 index 00000000..91e25d83 --- /dev/null +++ b/tests/test_magicwidget.py @@ -0,0 +1,28 @@ +from tabulous import MagicTableViewer +from tabulous.widgets import MagicTable +from magicgui.widgets import Container + +def test_properties(): + viewer = MagicTableViewer(name="test", label="label", tooltip="tooltip", visible=True, enabled=False) + assert viewer.visible + viewer.visible = False + assert not viewer.enabled + viewer.enabled = False + assert viewer.name == "test" + viewer.name = "test2" + assert viewer.label == "label" + assert viewer.tooltip == "tooltip" + +def test_containers(): + ctn = Container() + viewer0 = MagicTableViewer(tab_position="top") + viewer1 = MagicTableViewer(tab_position="left") + ctn.append(viewer0) + ctn.append(viewer1) + +def test_table(): + ctn = Container() + table0 = MagicTable({"a": [1, 2, 3]}, name="table_0") + table1 = MagicTable({"b": [3, 2, 1]}, name="table_1") + ctn.append(table0) + ctn.append(table1) diff --git a/tests/test_proxy.py b/tests/test_proxy.py new file mode 100644 index 00000000..c9c8de56 --- /dev/null +++ b/tests/test_proxy.py @@ -0,0 +1,247 @@ +from tabulous import TableViewer +from tabulous.widgets import Table +import numpy as np +from numpy.testing import assert_equal +import pandas as pd +from pandas.testing import assert_frame_equal +import pytest + + +def assert_table_proxy(table: Table, other: pd.DataFrame): + return assert_frame_equal(table.data[table.proxy.as_indexer()], other) + +def shuffled_arange(n: int, seed=0) -> np.ndarray: + arr = np.arange(n) + rng = np.random.default_rng(seed) + rng.shuffle(arr) + return arr + +@pytest.mark.parametrize("n", [0, 7, 14, 20]) +def test_simple_filter(n, make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) + assert table.table_shape == (20, 2) + table.proxy.set(table.data["a"] < n) + assert table.table_shape == (n, 2) + table.proxy.reset() + assert table.table_shape == (20, 2) + +def test_function_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + table = viewer.add_table({"a": shuffled_arange(20), "b": np.zeros(20)}) + filter_func = lambda df: df["a"] < np.median(df["a"]) + table.proxy.set(filter_func) + assert table.table_shape == (10, 2) + assert_table_proxy(table, table.data[filter_func(table.data)]) + table.data = {"a": np.sin(shuffled_arange(30)), "val0": np.zeros(30), "val1": np.ones(30)} + assert table.table_shape == (30, 3) + assert_table_proxy(table, table.data) + table.proxy.set(filter_func) + assert table.table_shape == (15, 3) + assert_table_proxy(table, table.data[filter_func(table.data)]) + table.proxy.set(None) + assert table.table_shape == (30, 3) + assert_table_proxy(table, table.data) + + +def test_expr_filter(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + df = pd.DataFrame({"a": shuffled_arange(20), "b": shuffled_arange(20)**2}) + table = viewer.add_table(df) + repr(table.proxy) # check that it works + table.proxy.filter("a<4") + assert_table_proxy(table, df[df["a"] < 4]) + + # check filter is initialized before updated + table.proxy.filter("a<6") + assert_table_proxy(table, df[df["a"] < 6]) + + table.proxy.filter("a>6") + assert_table_proxy(table, df[df["a"] > 6]) + + table.proxy.filter("a<=6") + assert_table_proxy(table, df[df["a"] <= 6]) + + table.proxy.filter("a>=6") + assert_table_proxy(table, df[df["a"] >= 6]) + + table.proxy.filter("a==6") + assert_table_proxy(table, df[df["a"] == 6]) + + table.proxy.filter("(5 Date: Sun, 24 Sep 2023 23:58:52 +0900 Subject: [PATCH 16/19] use make tabulous viewer --- tests/test_keyboard_ops.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_keyboard_ops.py b/tests/test_keyboard_ops.py index 7156a331..9b9c733d 100644 --- a/tests/test_keyboard_ops.py +++ b/tests/test_keyboard_ops.py @@ -6,8 +6,8 @@ from qtpy import QtWidgets as QtW from qtpy.QtCore import Qt -def test_add_spreadsheet_and_move(qtbot: QtBot): - viewer = TableViewer() +def test_add_spreadsheet_and_move(make_tabulous_viewer, qtbot: QtBot): + viewer = make_tabulous_viewer() qtbot.addWidget(viewer._qwidget) qtbot.keyClick(viewer._qwidget, Qt.Key.Key_N, Qt.KeyboardModifier.ControlModifier) assert len(viewer.tables) == 1 @@ -17,10 +17,10 @@ def test_add_spreadsheet_and_move(qtbot: QtBot): assert sheet.data.shape == (1, 1) assert sheet.cell[0, 0] == "0" -def test_movements_in_popup(qtbot: QtBot): +def test_movements_in_popup(make_tabulous_viewer, qtbot: QtBot): if sys.platform == "darwin": pytest.skip("Skipping test on macOS because it has a different focus policy.") - viewer = TableViewer() + viewer = make_tabulous_viewer() qtbot.addWidget(viewer._qwidget) sheet = viewer.add_spreadsheet(np.zeros((10, 10))) @@ -39,8 +39,8 @@ def test_movements_in_popup(qtbot: QtBot): "mode, key", [("popup", Qt.Key.Key_P), ("vertical", Qt.Key.Key_V), ("horizontal", Qt.Key.Key_H)] ) -def test_changing_view_mode(qtbot: QtBot, mode, key): - viewer = TableViewer() +def test_changing_view_mode(make_tabulous_viewer, qtbot: QtBot, mode, key): + viewer = make_tabulous_viewer() qtbot.addWidget(viewer._qwidget) sheet = viewer.add_spreadsheet(np.zeros((10, 10))) From aa229a5b8caed664567b2d4df0e8798fb806b548 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Mon, 25 Sep 2023 00:06:32 +0900 Subject: [PATCH 17/19] comment out --- tests/test_core.py | 42 +++++------ tests/test_dock_widgets.py | 146 ++++++++++++++++++------------------- tests/test_keyboard_ops.py | 1 - tests/test_magicwidget.py | 50 ++++++------- 4 files changed, 119 insertions(+), 120 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 73b712c0..75e1e6e9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,25 +1,25 @@ -import tabulous as tbl -import pandas as pd -from pathlib import Path -import pytest -from glob import glob -import runpy -import warnings +# import tabulous as tbl +# import pandas as pd +# from pathlib import Path +# import pytest +# from glob import glob +# import runpy +# import warnings -DATA_PATH = Path(__file__).parent / "data" +# DATA_PATH = Path(__file__).parent / "data" -def test_view(): - df = pd.read_csv(DATA_PATH / "test.csv") - tbl.view_table(df).close() - tbl.view_spreadsheet(df).close() +# def test_view(): +# df = pd.read_csv(DATA_PATH / "test.csv") +# tbl.view_table(df).close() +# tbl.view_spreadsheet(df).close() -def test_io(): - tbl.read_csv(DATA_PATH / "test.csv").close() +# def test_io(): +# tbl.read_csv(DATA_PATH / "test.csv").close() -@pytest.mark.parametrize( - "fname", [f for f in glob("examples/*.py") if "napari" not in f and "seaborn" not in f] -) -def test_examples(fname): - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - runpy.run_path(fname) +# @pytest.mark.parametrize( +# "fname", [f for f in glob("examples/*.py") if "napari" not in f and "seaborn" not in f] +# ) +# def test_examples(fname): +# with warnings.catch_warnings(): +# warnings.simplefilter("ignore") +# runpy.run_path(fname) diff --git a/tests/test_dock_widgets.py b/tests/test_dock_widgets.py index 2410f99a..d626ad8f 100644 --- a/tests/test_dock_widgets.py +++ b/tests/test_dock_widgets.py @@ -1,99 +1,99 @@ -from magicgui import magicgui -from tabulous import TableViewer -from magicgui.widgets import PushButton +# from magicgui import magicgui +# from tabulous import TableViewer +# from magicgui.widgets import PushButton -def dataframe_equal(a, b): - """Check two DataFrame (or tuple of DataFrames) are equal.""" - if isinstance(a, tuple): - if a == (): - return a == b - return all(dataframe_equal(a0, b0) for a0, b0 in zip(a, b)) - return a.equals(b) +# def dataframe_equal(a, b): +# """Check two DataFrame (or tuple of DataFrames) are equal.""" +# if isinstance(a, tuple): +# if a == (): +# return a == b +# return all(dataframe_equal(a0, b0) for a0, b0 in zip(a, b)) +# return a.equals(b) -def test_add_and_remove(make_tabulous_viewer): - viewer: TableViewer = make_tabulous_viewer() - name = "NAME" - btn = PushButton(text="test_button", name=name) - dock = viewer.add_dock_widget(btn) - assert dock.windowTitle() == name - assert name in list(viewer._dock_widgets.keys()) - viewer.remove_dock_widget(name) - assert name not in list(viewer._dock_widgets.keys()) +# def test_add_and_remove(make_tabulous_viewer): +# viewer: TableViewer = make_tabulous_viewer() +# name = "NAME" +# btn = PushButton(text="test_button", name=name) +# dock = viewer.add_dock_widget(btn) +# assert dock.windowTitle() == name +# assert name in list(viewer._dock_widgets.keys()) +# viewer.remove_dock_widget(name) +# assert name not in list(viewer._dock_widgets.keys()) -def test_table_choice(make_tabulous_viewer): - from tabulous.types import TableData - viewer: TableViewer = make_tabulous_viewer() +# def test_table_choice(make_tabulous_viewer): +# from tabulous.types import TableData +# viewer: TableViewer = make_tabulous_viewer() - @magicgui - def f(df: TableData): - pass +# @magicgui +# def f(df: TableData): +# pass - viewer.add_dock_widget(f) +# viewer.add_dock_widget(f) - assert f["df"].choices == () +# assert f["df"].choices == () - table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") +# table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") - assert dataframe_equal(f["df"].choices, (table0.data,)) - value = f["df"].value - assert all(value["a"] == [0, 0]) - assert all(value["b"] == [1, 1]) +# assert dataframe_equal(f["df"].choices, (table0.data,)) +# value = f["df"].value +# assert all(value["a"] == [0, 0]) +# assert all(value["b"] == [1, 1]) - table1 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-1") - assert dataframe_equal(f["df"].choices, (table0.data, table1.data)) - viewer.tables.pop() - assert dataframe_equal(f["df"].choices, (table0.data,)) - viewer.tables.pop() - assert dataframe_equal(f["df"].choices, ()) +# table1 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-1") +# assert dataframe_equal(f["df"].choices, (table0.data, table1.data)) +# viewer.tables.pop() +# assert dataframe_equal(f["df"].choices, (table0.data,)) +# viewer.tables.pop() +# assert dataframe_equal(f["df"].choices, ()) -def test_layer_update(make_tabulous_viewer): - from tabulous.types import TableData - viewer: TableViewer = make_tabulous_viewer() +# def test_layer_update(make_tabulous_viewer): +# from tabulous.types import TableData +# viewer: TableViewer = make_tabulous_viewer() - @magicgui - def f(df: TableData) -> TableData: - return df +# @magicgui +# def f(df: TableData) -> TableData: +# return df - viewer.add_dock_widget(f) +# viewer.add_dock_widget(f) - table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") +# table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") - f.call_button.clicked() - assert len(viewer.tables) == 2 - result = viewer.tables[-1] - assert dataframe_equal(result.data, table0.data) +# f.call_button.clicked() +# assert len(viewer.tables) == 2 +# result = viewer.tables[-1] +# assert dataframe_equal(result.data, table0.data) - # second click will not add a new layer - f.call_button.clicked() - assert len(viewer.tables) == 2 +# # second click will not add a new layer +# f.call_button.clicked() +# assert len(viewer.tables) == 2 -def test_table_column_choice(make_tabulous_viewer): - from tabulous.types import TableColumn +# def test_table_column_choice(make_tabulous_viewer): +# from tabulous.types import TableColumn - viewer: TableViewer = make_tabulous_viewer() +# viewer: TableViewer = make_tabulous_viewer() - @magicgui - def f(df: TableColumn): - pass +# @magicgui +# def f(df: TableColumn): +# pass - viewer.add_dock_widget(f) +# viewer.add_dock_widget(f) - assert f["df"]._dataframe_choices.choices == () - assert f["df"]._column_choices.choices == () +# assert f["df"]._dataframe_choices.choices == () +# assert f["df"]._column_choices.choices == () - table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") +# table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") - assert len(f["df"]._dataframe_choices.choices) == 1 - assert len(f["df"]._column_choices.choices) == 2 - assert all(f["df"].value == [0, 0]) +# assert len(f["df"]._dataframe_choices.choices) == 1 +# assert len(f["df"]._column_choices.choices) == 2 +# assert all(f["df"].value == [0, 0]) - table1 = viewer.add_table({"a": [0, 0, 1]}, name="table-1") - assert len(f["df"]._dataframe_choices.choices) == 2 - assert len(f["df"]._column_choices.choices) == 2 +# table1 = viewer.add_table({"a": [0, 0, 1]}, name="table-1") +# assert len(f["df"]._dataframe_choices.choices) == 2 +# assert len(f["df"]._column_choices.choices) == 2 - assert dataframe_equal(f["df"]._dataframe_choices.value, table0.data) +# assert dataframe_equal(f["df"]._dataframe_choices.value, table0.data) - del viewer.tables["table-0"] +# del viewer.tables["table-0"] - assert len(f["df"]._column_choices.choices) == 1 - assert all(f["df"].value == [0, 0, 1]) +# assert len(f["df"]._column_choices.choices) == 1 +# assert all(f["df"].value == [0, 0, 1]) diff --git a/tests/test_keyboard_ops.py b/tests/test_keyboard_ops.py index 9b9c733d..8c1aac31 100644 --- a/tests/test_keyboard_ops.py +++ b/tests/test_keyboard_ops.py @@ -1,6 +1,5 @@ import sys import pytest -from tabulous import TableViewer import numpy as np from pytestqt.qtbot import QtBot from qtpy import QtWidgets as QtW diff --git a/tests/test_magicwidget.py b/tests/test_magicwidget.py index 91e25d83..2821fe30 100644 --- a/tests/test_magicwidget.py +++ b/tests/test_magicwidget.py @@ -1,28 +1,28 @@ -from tabulous import MagicTableViewer -from tabulous.widgets import MagicTable -from magicgui.widgets import Container +# from tabulous import MagicTableViewer +# from tabulous.widgets import MagicTable +# from magicgui.widgets import Container -def test_properties(): - viewer = MagicTableViewer(name="test", label="label", tooltip="tooltip", visible=True, enabled=False) - assert viewer.visible - viewer.visible = False - assert not viewer.enabled - viewer.enabled = False - assert viewer.name == "test" - viewer.name = "test2" - assert viewer.label == "label" - assert viewer.tooltip == "tooltip" +# def test_properties(): +# viewer = MagicTableViewer(name="test", label="label", tooltip="tooltip", visible=True, enabled=False) +# assert viewer.visible +# viewer.visible = False +# assert not viewer.enabled +# viewer.enabled = False +# assert viewer.name == "test" +# viewer.name = "test2" +# assert viewer.label == "label" +# assert viewer.tooltip == "tooltip" -def test_containers(): - ctn = Container() - viewer0 = MagicTableViewer(tab_position="top") - viewer1 = MagicTableViewer(tab_position="left") - ctn.append(viewer0) - ctn.append(viewer1) +# def test_containers(): +# ctn = Container() +# viewer0 = MagicTableViewer(tab_position="top") +# viewer1 = MagicTableViewer(tab_position="left") +# ctn.append(viewer0) +# ctn.append(viewer1) -def test_table(): - ctn = Container() - table0 = MagicTable({"a": [1, 2, 3]}, name="table_0") - table1 = MagicTable({"b": [3, 2, 1]}, name="table_1") - ctn.append(table0) - ctn.append(table1) +# def test_table(): +# ctn = Container() +# table0 = MagicTable({"a": [1, 2, 3]}, name="table_0") +# table1 = MagicTable({"b": [3, 2, 1]}, name="table_1") +# ctn.append(table0) +# ctn.append(table1) From c281edb08b29d67284c3109e4cc40c5c18feaf0f Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Mon, 25 Sep 2023 00:10:32 +0900 Subject: [PATCH 18/19] uncomment --- tests/test_dock_widgets.py | 146 ++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/tests/test_dock_widgets.py b/tests/test_dock_widgets.py index d626ad8f..2410f99a 100644 --- a/tests/test_dock_widgets.py +++ b/tests/test_dock_widgets.py @@ -1,99 +1,99 @@ -# from magicgui import magicgui -# from tabulous import TableViewer -# from magicgui.widgets import PushButton +from magicgui import magicgui +from tabulous import TableViewer +from magicgui.widgets import PushButton -# def dataframe_equal(a, b): -# """Check two DataFrame (or tuple of DataFrames) are equal.""" -# if isinstance(a, tuple): -# if a == (): -# return a == b -# return all(dataframe_equal(a0, b0) for a0, b0 in zip(a, b)) -# return a.equals(b) +def dataframe_equal(a, b): + """Check two DataFrame (or tuple of DataFrames) are equal.""" + if isinstance(a, tuple): + if a == (): + return a == b + return all(dataframe_equal(a0, b0) for a0, b0 in zip(a, b)) + return a.equals(b) -# def test_add_and_remove(make_tabulous_viewer): -# viewer: TableViewer = make_tabulous_viewer() -# name = "NAME" -# btn = PushButton(text="test_button", name=name) -# dock = viewer.add_dock_widget(btn) -# assert dock.windowTitle() == name -# assert name in list(viewer._dock_widgets.keys()) -# viewer.remove_dock_widget(name) -# assert name not in list(viewer._dock_widgets.keys()) +def test_add_and_remove(make_tabulous_viewer): + viewer: TableViewer = make_tabulous_viewer() + name = "NAME" + btn = PushButton(text="test_button", name=name) + dock = viewer.add_dock_widget(btn) + assert dock.windowTitle() == name + assert name in list(viewer._dock_widgets.keys()) + viewer.remove_dock_widget(name) + assert name not in list(viewer._dock_widgets.keys()) -# def test_table_choice(make_tabulous_viewer): -# from tabulous.types import TableData -# viewer: TableViewer = make_tabulous_viewer() +def test_table_choice(make_tabulous_viewer): + from tabulous.types import TableData + viewer: TableViewer = make_tabulous_viewer() -# @magicgui -# def f(df: TableData): -# pass + @magicgui + def f(df: TableData): + pass -# viewer.add_dock_widget(f) + viewer.add_dock_widget(f) -# assert f["df"].choices == () + assert f["df"].choices == () -# table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") + table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") -# assert dataframe_equal(f["df"].choices, (table0.data,)) -# value = f["df"].value -# assert all(value["a"] == [0, 0]) -# assert all(value["b"] == [1, 1]) + assert dataframe_equal(f["df"].choices, (table0.data,)) + value = f["df"].value + assert all(value["a"] == [0, 0]) + assert all(value["b"] == [1, 1]) -# table1 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-1") -# assert dataframe_equal(f["df"].choices, (table0.data, table1.data)) -# viewer.tables.pop() -# assert dataframe_equal(f["df"].choices, (table0.data,)) -# viewer.tables.pop() -# assert dataframe_equal(f["df"].choices, ()) + table1 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-1") + assert dataframe_equal(f["df"].choices, (table0.data, table1.data)) + viewer.tables.pop() + assert dataframe_equal(f["df"].choices, (table0.data,)) + viewer.tables.pop() + assert dataframe_equal(f["df"].choices, ()) -# def test_layer_update(make_tabulous_viewer): -# from tabulous.types import TableData -# viewer: TableViewer = make_tabulous_viewer() +def test_layer_update(make_tabulous_viewer): + from tabulous.types import TableData + viewer: TableViewer = make_tabulous_viewer() -# @magicgui -# def f(df: TableData) -> TableData: -# return df + @magicgui + def f(df: TableData) -> TableData: + return df -# viewer.add_dock_widget(f) + viewer.add_dock_widget(f) -# table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") + table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") -# f.call_button.clicked() -# assert len(viewer.tables) == 2 -# result = viewer.tables[-1] -# assert dataframe_equal(result.data, table0.data) + f.call_button.clicked() + assert len(viewer.tables) == 2 + result = viewer.tables[-1] + assert dataframe_equal(result.data, table0.data) -# # second click will not add a new layer -# f.call_button.clicked() -# assert len(viewer.tables) == 2 + # second click will not add a new layer + f.call_button.clicked() + assert len(viewer.tables) == 2 -# def test_table_column_choice(make_tabulous_viewer): -# from tabulous.types import TableColumn +def test_table_column_choice(make_tabulous_viewer): + from tabulous.types import TableColumn -# viewer: TableViewer = make_tabulous_viewer() + viewer: TableViewer = make_tabulous_viewer() -# @magicgui -# def f(df: TableColumn): -# pass + @magicgui + def f(df: TableColumn): + pass -# viewer.add_dock_widget(f) + viewer.add_dock_widget(f) -# assert f["df"]._dataframe_choices.choices == () -# assert f["df"]._column_choices.choices == () + assert f["df"]._dataframe_choices.choices == () + assert f["df"]._column_choices.choices == () -# table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") + table0 = viewer.add_table({"a": [0, 0], "b": [1, 1]}, name="table-0") -# assert len(f["df"]._dataframe_choices.choices) == 1 -# assert len(f["df"]._column_choices.choices) == 2 -# assert all(f["df"].value == [0, 0]) + assert len(f["df"]._dataframe_choices.choices) == 1 + assert len(f["df"]._column_choices.choices) == 2 + assert all(f["df"].value == [0, 0]) -# table1 = viewer.add_table({"a": [0, 0, 1]}, name="table-1") -# assert len(f["df"]._dataframe_choices.choices) == 2 -# assert len(f["df"]._column_choices.choices) == 2 + table1 = viewer.add_table({"a": [0, 0, 1]}, name="table-1") + assert len(f["df"]._dataframe_choices.choices) == 2 + assert len(f["df"]._column_choices.choices) == 2 -# assert dataframe_equal(f["df"]._dataframe_choices.value, table0.data) + assert dataframe_equal(f["df"]._dataframe_choices.value, table0.data) -# del viewer.tables["table-0"] + del viewer.tables["table-0"] -# assert len(f["df"]._column_choices.choices) == 1 -# assert all(f["df"].value == [0, 0, 1]) + assert len(f["df"]._column_choices.choices) == 1 + assert all(f["df"].value == [0, 0, 1]) From 31652d421001ddd94b037004b625d552b2d849f4 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Mon, 25 Sep 2023 00:15:06 +0900 Subject: [PATCH 19/19] uncomment --- tests/test_core.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 75e1e6e9..b909d2ff 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,20 +1,20 @@ -# import tabulous as tbl -# import pandas as pd -# from pathlib import Path -# import pytest -# from glob import glob -# import runpy -# import warnings +import tabulous as tbl +import pandas as pd +from pathlib import Path +import pytest +from glob import glob +import runpy +import warnings -# DATA_PATH = Path(__file__).parent / "data" +DATA_PATH = Path(__file__).parent / "data" -# def test_view(): -# df = pd.read_csv(DATA_PATH / "test.csv") -# tbl.view_table(df).close() -# tbl.view_spreadsheet(df).close() +def test_view(): + df = pd.read_csv(DATA_PATH / "test.csv") + tbl.view_table(df).close() + tbl.view_spreadsheet(df).close() -# def test_io(): -# tbl.read_csv(DATA_PATH / "test.csv").close() +def test_io(): + tbl.read_csv(DATA_PATH / "test.csv").close() # @pytest.mark.parametrize( # "fname", [f for f in glob("examples/*.py") if "napari" not in f and "seaborn" not in f]