Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UITester support for TableEditor #1707

Merged
merged 30 commits into from Feb 3, 2022
Merged

Conversation

aaronayres35
Copy link
Contributor

@aaronayres35 aaronayres35 commented Jun 29, 2021

After looking though these changes again, I felt like this branch (same as #1706) really was a good starting point. I went ahead and rebased on top of master, and will clean up this PR as needed with the intention that this ultimately will be merged.

I'm struggling to remember why I hadn't opened a PR for this previously. I suspect it was that the wx implementation is incomplete. Qt I believe is fully functional (?)
wx has allowed failures on CI, and many TableEditor tests already fail. I remember this making adding functionality difficult because even if I thought i had wx support working tests may have still failed for other reasons...

EDIT: I am going to remove any wx changes in this PR and defer adding wx support for TableEditor to a later PR
Checklist

  • Add a news fragment if this PR is news-worthy for end users. (see docs/releases/README.rst)

Note that the index error provides more
"""
check_q_model_index_valid(index)
return model.data(index, QtCore.Qt.DisplayRole)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is data the right thing here for displayed text? (look at Qt docs for QAbstractItemModel)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, data with DisplayRole should provide the text to display.

@rahulporuri rahulporuri self-requested a review June 30, 2021 09:56
@aaronayres35
Copy link
Contributor Author

On master the following TableEditor Tests fail on wx:

test_filtered_table_editor
test_table_editor
test_table_editor_select_cell
test_table_editor_select_cell_index
test_table_editor_select_cell_indices
test_table_editor_select_cells
test_table_editor_select_column
test_table_editor_select_column_index
test_table_editor_select_column_indices
test_table_editor_select_columns
test_table_editor_select_row
test_table_editor_select_row_index
test_table_editor_select_row_indices
test_table_editor_select_rows

(Note this is effectively every test - I run python -m unittest traitsui/tests/editors/test_table_editor.py -v in an env created with python etstool.py install --toolkit=wx --editable and I see 16 tests run with 14 errors and 2 skipped tests.

Given this, I intend to skip all these tests on wx. Note this is now a part of #1533. Even further, I intend to rewrite the tests to use the new UI Tester support on Qt. Thus, even if the TableEditor issues are fixed, we will need to follow through with the UI Tester support to simply stop skipping these tests on wx. Either that or use this PR as a ref and revert the tests back to their old non UITester using state. I am doing this based off the following section of the developer guide in the traitsui docs: https://docs.enthought.com/traitsui/traitsui_dev_guide/testing.html#where-are-the-tests


@requires_toolkit([ToolkitName.qt, ToolkitName.wx])
def test_table_editor_select_rows(self):
object_list = ObjectListWithSelection(
values=[ListItem(value=str(i ** 2)) for i in range(10)]
)
object_list.selections = object_list.values[5:7]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have shift click functionality to do this sort of thing with the tester...

target_class=SimpleEditor,
interaction_class=SelectedIndices,
handler=lambda wrapper, _: wrapper._target.selected_indices
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just added a SelectedIndices query class.
For the current implementation, for TableEditor, Selected and SelectedIndices effectively just serve as proxies to the selected and selected_indices traits on the table editor (see code above.)
In theory it may be better for these to dive down to the Qt level (?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the weird things with UITester support though IMO. We want to test traitsui editors are working correctly.
So having tests be down at qt level is really what we want. Ie I do something in traitsUI and we verify that does what we expect down in qt land.
However, say I were implementing the handler for the TableEditor to do a SelectedIndices query. I would end up basically rewriting:

@cached_property
def _get_selected_indices(self):
"""Gets the row,column indices which match the selected trait"""
selection_items = self.table_view.selectionModel().selection()
indices = self.model.mapSelectionToSource(selection_items).indexes()
if self.factory.selection_mode.startswith("row"):
indices = sorted(set(index.row() for index in indices))
elif self.factory.selection_mode.startswith("column"):
indices = sorted(set(index.column() for index in indices))
else:
indices = [(index.row(), index.column()) for index in indices]
if self.factory.selection_mode in {"rows", "columns", "cells"}:
return indices
elif len(indices) > 0:
return indices[0]
else:
return -1

It creates sort of a weird chasing your own tail scenario 🤔

For downstream users they just want to see that when they run their code, ____ is selected. For which this (just using traits on the editor) is perfectly fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UITester strikes me as being useful for two things:

  • testing that TraitsUI editors are integrating correctly with the toolkit
  • as a framework for integration tests for TraitsUI apps

For unit tests of applications you can usually get away with tests at the traits level (does changing this trait in a UI change the corresponding thing in the model? or vice-versa) where you don't really care/can trust that the UI then does the right thing.

@aaronayres35
Copy link
Contributor Author

I believe once this PR gets the green light, a lot of the code can be pulled to add support for TabularEditor which also ends up wrapping a QTableView. Likewise that will probably get only qt support first as wx tests are currently all failing.

traitsui/testing/api.py Show resolved Hide resolved
traitsui/testing/tester/query.py Outdated Show resolved Hide resolved
traitsui/testing/tester/query.py Outdated Show resolved Hide resolved
Comment on lines 33 to 35
Implementations should return either a list of indicies of the selected
objects, a single index of a lone selected object, or -1 if nothing is
selected.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the rationality behind this API? Why not just return None if nothing is selected? Also, why not always return a list - which might contain one or more elements depending on the size of the selection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH it was because I am currently just mirroring the implementation of selected_indices in the TableEditor itself. See comment: #1707 (comment) and code:

def _get_selected_indices(self):
"""Gets the row,column indices which match the selected trait"""
selection_items = self.table_view.selectionModel().selection()
indices = self.model.mapSelectionToSource(selection_items).indexes()
if self.factory.selection_mode.startswith("row"):
indices = sorted(set(index.row() for index in indices))
elif self.factory.selection_mode.startswith("column"):
indices = sorted(set(index.column() for index in indices))
else:
indices = [(index.row(), index.column()) for index in indices]
if self.factory.selection_mode in {"rows", "columns", "cells"}:
return indices
elif len(indices) > 0:
return indices[0]
else:
return -1

I agree this is confusing though and lazy. I can at the very least wrap this with a special handler to give a more consistent return type. The SelectedIndices query class has no reason to be at all tied with a specific Editor (in fact it definitely shouldn't be).

Nonetheless I do still have the question of whether or not I should be reimplementing/rewriting the above code in the UITester TableEditor support code as a specifc handler for the SelectedIndices query. As mentioned in my comment I think that is a little unnecessary / we have this strange chase your own tail scenario... 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to carefully think about the best api for Selected and SelectedIndices before pushing this through 🤔

It i tricky because with a TableEditor, one could have rows, columns, cells, etc. selected. The api needs to accommodate this use case but also be general and not overly complex for other Editors to use

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to update Selected so that it also always returns a list (either a singleton list for one selection, list of all objects selected, or an empty list if nothing is selected).
It is also a bit strange with SelectedText as in that case it doesn't really make sense to want to make that query if multiple objects are selected (at least I can't imagine a use case). Hence for that I think I will not update it to enforce that it returns a list.

Comment on lines 37 to 38
Note that an index could be an integer, or it could be a tuple e.g.
corresponding to a specific cell at some (row, column) in a table.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

under what circumstances do we return an integer vs a tuple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, it would be an int if selecting entire rows or columns, whereas it would be a tuple if selecting specific cells. However, if used with say a ListEditor, InstanceEditor, or EnumEditor, a selected index would likely be an integer. Currently those implementations do not exist (in current tests querying what is selected we just use SelectedText rather than inspecting the object itself that is selected). These implementations can be added in the future though.
I've updated the doc string to try to make this more clear. ~(will push commit shortly)~Edit: done

@rahulporuri rahulporuri self-requested a review July 6, 2021 06:13

process_cascade_events()
self.assertEqual(selected, [object_list.values[5]])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the list here slightly clunkier than before... However, i think being consistent with the api of SelectedIndices is more important.

@corranwebster corranwebster added this to the Release 7.3.0 milestone Feb 1, 2022
Copy link
Member

@corranwebster corranwebster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good at a high level. From examples of usage, looks like it is reasonably user-friendly.

@corranwebster corranwebster merged commit 360ca46 into main Feb 3, 2022
@corranwebster corranwebster deleted the ui-tester-updates-TableEditor branch February 3, 2022 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants