hsgui / tests / table_test.py

# Created By: Virgil Dupras
# Created On: 2008-08-12
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
# 
# This software is licensed under the "HS" License as described in the "LICENSE" file, 
# which should be included with this package. The terms are also available at 
# http://www.hardcoded.net/licenses/hs_license

from nose.tools import eq_

from ..table import Table, GUITable, Row

class TableView(object):
    def refresh(self):
        pass
    
    def start_editing(self):
        pass
    
    def stop_editing(self):
        pass
    

class TestRow(Row):
    def __init__(self, table, index, is_new=False):
        Row.__init__(self, table)
        self.is_new = is_new
        self._index = index
    
    def load(self):
        pass
    
    def save(self):
        self.is_new = False
    
    @property
    def index(self):
        return self._index
    

class TestGUITable(GUITable):
    def __init__(self, rowcount):
        GUITable.__init__(self)
        self.view = TableView()
        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_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_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_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
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.