# Created By: Virgil Dupras # Created On: 2008-08-12 # Copyright 2015 Hardcoded Software (http://www.hardcoded.net) # # This software is licensed under the "GPLv3" License as described in the "LICENSE" file, # which should be included with this package. The terms are also available at # http://www.gnu.org/licenses/gpl-3.0.html from hscommon.testutil import CallLogger, eq_ from hscommon.gui.table import Table, GUITable, Row class TestRow(Row): __test__ = False def __init__(self, table, index, is_new=False): Row.__init__(self, table) self.is_new = is_new self._index = index def load(self): # Does nothing for test pass def save(self): self.is_new = False @property def index(self): return self._index class TestGUITable(GUITable): __test__ = False def __init__(self, rowcount, viewclass=CallLogger): GUITable.__init__(self) self.view = viewclass() self.view.model = self self.rowcount = rowcount self.updated_rows = None def _do_add(self): return TestRow(self, len(self), is_new=True), len(self) def _is_edited_new(self): return self.edited is not None and self.edited.is_new def _fill(self): for i in range(self.rowcount): self.append(TestRow(self, i)) def _update_selection(self): self.updated_rows = self.selected_rows[:] def table_with_footer(): table = Table() table.append(TestRow(table, 0)) footer = TestRow(table, 1) table.footer = footer return table, footer def table_with_header(): table = Table() table.append(TestRow(table, 1)) header = TestRow(table, 0) table.header = header return table, header # --- Tests def test_allow_edit_when_attr_is_property_with_fset(): # When a row has a property that has a fset, by default, make that cell editable. class TestRow(Row): @property def foo(self): # property only for existence checks pass @property def bar(self): # property only for existence checks pass @bar.setter def bar(self, value): # setter only for existence checks pass row = TestRow(Table()) assert row.can_edit_cell("bar") assert not row.can_edit_cell("foo") assert not row.can_edit_cell("baz") # doesn't exist, can't edit def test_can_edit_prop_has_priority_over_fset_checks(): # When a row has a cen_edit_* property, it's the result of that property that is used, not the # result of a fset check. class TestRow(Row): @property def bar(self): # property only for existence checks pass @bar.setter def bar(self, value): # setter only for existence checks pass can_edit_bar = False row = TestRow(Table()) assert not row.can_edit_cell("bar") def test_in(): # When a table is in a list, doing "in list" with another instance returns false, even if # they're the same as lists. table = Table() some_list = [table] assert Table() not in some_list def test_footer_del_all(): # Removing all rows doesn't crash when doing the footer check. table, footer = table_with_footer() del table[:] assert table.footer is None def test_footer_del_row(): # Removing the footer row sets it to None table, footer = table_with_footer() del table[-1] assert table.footer is None eq_(len(table), 1) def test_footer_is_appened_to_table(): # A footer is appended at the table's bottom table, footer = table_with_footer() eq_(len(table), 2) assert table[1] is footer def test_footer_remove(): # remove() on footer sets it to None table, footer = table_with_footer() table.remove(footer) assert table.footer is None def test_footer_replaces_old_footer(): table, footer = table_with_footer() other = Row(table) table.footer = other assert table.footer is other eq_(len(table), 2) assert table[1] is other def test_footer_rows_and_row_count(): # rows() and row_count() ignore footer. table, footer = table_with_footer() eq_(table.row_count, 1) eq_(table.rows, table[:-1]) def test_footer_setting_to_none_removes_old_one(): table, footer = table_with_footer() table.footer = None assert table.footer is None eq_(len(table), 1) def test_footer_stays_there_on_append(): # Appending another row puts it above the footer table, footer = table_with_footer() table.append(Row(table)) eq_(len(table), 3) assert table[2] is footer def test_footer_stays_there_on_insert(): # Inserting another row puts it above the footer table, footer = table_with_footer() table.insert(3, Row(table)) eq_(len(table), 3) assert table[2] is footer def test_header_del_all(): # Removing all rows doesn't crash when doing the header check. table, header = table_with_header() del table[:] assert table.header is None def test_header_del_row(): # Removing the header row sets it to None table, header = table_with_header() del table[0] assert table.header is None eq_(len(table), 1) def test_header_is_inserted_in_table(): # A header is inserted at the table's top table, header = table_with_header() eq_(len(table), 2) assert table[0] is header def test_header_remove(): # remove() on header sets it to None table, header = table_with_header() table.remove(header) assert table.header is None def test_header_replaces_old_header(): table, header = table_with_header() other = Row(table) table.header = other assert table.header is other eq_(len(table), 2) assert table[0] is other def test_header_rows_and_row_count(): # rows() and row_count() ignore header. table, header = table_with_header() eq_(table.row_count, 1) eq_(table.rows, table[1:]) def test_header_setting_to_none_removes_old_one(): table, header = table_with_header() table.header = None assert table.header is None eq_(len(table), 1) def test_header_stays_there_on_insert(): # Inserting another row at the top puts it below the header table, header = table_with_header() table.insert(0, Row(table)) eq_(len(table), 3) assert table[0] is header def test_refresh_view_on_refresh(): # If refresh_view is not False, we refresh the table's view on refresh() table = TestGUITable(1) table.refresh() table.view.check_gui_calls(["refresh"]) table.view.clear_calls() table.refresh(refresh_view=False) table.view.check_gui_calls([]) def test_restore_selection(): # By default, after a refresh, selection goes on the last row table = TestGUITable(10) table.refresh() eq_(table.selected_indexes, [9]) def test_restore_selection_after_cancel_edits(): # _restore_selection() is called after cancel_edits(). Previously, only _update_selection would # be called. class MyTable(TestGUITable): def _restore_selection(self, previous_selection): self.selected_indexes = [6] table = MyTable(10) table.refresh() table.add() table.cancel_edits() eq_(table.selected_indexes, [6]) def test_restore_selection_with_previous_selection(): # By default, we try to restore the selection that was there before a refresh table = TestGUITable(10) table.refresh() table.selected_indexes = [2, 4] table.refresh() eq_(table.selected_indexes, [2, 4]) def test_restore_selection_custom(): # After a _fill() called, the virtual _restore_selection() is called so that it's possible for a # GUITable subclass to customize its post-refresh selection behavior. class MyTable(TestGUITable): def _restore_selection(self, previous_selection): self.selected_indexes = [6] table = MyTable(10) table.refresh() eq_(table.selected_indexes, [6]) def test_row_cell_value(): # *_cell_value() correctly mangles attrnames that are Python reserved words. row = Row(Table()) row.from_ = "foo" eq_(row.get_cell_value("from"), "foo") row.set_cell_value("from", "bar") eq_(row.get_cell_value("from"), "bar") def test_sort_table_also_tries_attributes_without_underscores(): # When determining a sort key, after having unsuccessfully tried the attribute with the, # underscore, try the one without one. table = Table() row1 = Row(table) row1._foo = "a" # underscored attr must be checked first row1.foo = "b" row1.bar = "c" row2 = Row(table) row2._foo = "b" row2.foo = "a" row2.bar = "b" table.append(row1) table.append(row2) table.sort_by("foo") assert table[0] is row1 assert table[1] is row2 table.sort_by("bar") assert table[0] is row2 assert table[1] is row1 def test_sort_table_updates_selection(): table = TestGUITable(10) table.refresh() table.select([2, 4]) table.sort_by("index", desc=True) # Now, the updated rows should be 7 and 5 eq_(len(table.updated_rows), 2) r1, r2 = table.updated_rows eq_(r1.index, 7) eq_(r2.index, 5) def test_sort_table_with_footer(): # Sorting a table with a footer keeps it at the bottom table, footer = table_with_footer() table.sort_by("index", desc=True) assert table[-1] is footer def test_sort_table_with_header(): # Sorting a table with a header keeps it at the top table, header = table_with_header() table.sort_by("index", desc=True) assert table[0] is header def test_add_with_view_that_saves_during_refresh(): # Calling save_edits during refresh() called by add() is ignored. class TableView(CallLogger): def refresh(self): self.model.save_edits() table = TestGUITable(10, viewclass=TableView) table.add() assert table.edited is not None # still in edit mode