How should a test be written for a delegate for editing? - testing

I have a tree view for which some fields need to use a custom delegate for editing. The delegate presents a QListView for value selection. It seems like the QAbstractItemView.edit() method should be used to initiate the edit from the test but I can't figure out how to get access to the created editor (a QListView) so I can select the proper element for the test.
This is part of a test I had working with a QComboBox delegate before switching to the QListVew, but it seems too manual.
for index, enumerator in enumerate(group.children):
editor = delegate.createEditor(
parent=viewport,
option=None,
index=target_index,
)
editor.setCurrentIndex(index)
delegate.setModelData(editor, model, target_index)
assert enumerator.uuid == item.enumeration_uuid
https://github.com/altendky/st/commit/643c5c30f87fc3bfd8b422687e81f740ec36ef44#diff-06bc81dbbd9f7a12878169d5238e1572R846

Here is what I came up with.
https://github.com/altendky/st/blob/089432162b9e8ca67eafdfcc2a4ecc34e8f0e96e/epyqlib/tests/test_attrsmodel.py#L848
for row, enumerator in enumerate(group.children):
assert view.edit(
target_index,
PyQt5.QtWidgets.QAbstractItemView.AllEditTriggers,
None,
)
editor, = view.findChildren(PyQt5.QtWidgets.QListView)
index = editor.model().index(row, 0, editor.rootIndex())
editor.setCurrentIndex(index)
editor.clicked.emit(index)
# this is fun. if you get weird issues try doing this more times
for _ in range(3):
application.processEvents()
assert enumerator.uuid == item.enumeration_uuid
Do note that I connect the editor's clicked signal to post an enter-key event since the view's are hardcoded to catch an enter event and finish editing.

Related

Why does NavigationView.ContainerFromMenuItem() return null except when executed from an event handler?

I am developing a WinUI3 app containing a NavigationView. The NV uses an observable collection to populate the NavigationViewItems.
The app must support expanding and collapsing NavigationViewItems programmatically. The only way I know of to do that is to set NavigationViewItem.IsExpanded = true. And the only way I know of to get the NavigationViewItem is through NavigationView.ContainerFromMenuItem().
The trouble is, ContainerFromMenuItem always returns null except when it is executed from a button event handler (such as a Click handler). The app must perform expand/collapse without user input.
For example, I have a button that launches a Click event with this code, which works just fine to toggle an NVItem:
Category selectedItem = (Category)navview.SelectedItem;
int idx1 = Categories.IndexOf(selectedItem);
var container = (NavigationViewItem)NavView.ContainerFromMenuItem(Categories[idx1]);
if (container != null)
{
container.IsExpanded = !container.IsExpanded;
}
However, that same code, when executed during app startup, such as in the MainWindow constructor after some test items are created, or in Attach(), always results in container being null.
So what am I doing wrong?
This question is somewhat similar to UWP: NavigationView.MenuItems results empty if populated programmatically but the answer to that one only deals with in-event use of ContainerFromMenuItem().
Thanks very much for any assistance on this.

How to use a for loop to iterate over dynamically named variables (var1, var2, ...)

I am trying to make a GUI using PyQt5.
I have a couple of buttons say 4 which call the same function. A click on a button calls the same function to plot the data associated with each button.
radiobutton1.clicked.connect(self.fun)
radiobutton2.clicked.connect(self.fun)
radiobutton3.clicked.connect(self.fun)
radiobutton4.clicked.connect(self.fun)
Since each line of code is the same except the name of the button, is there a way instead of writing almost the same code four times, I use a for loop to generate the code?
You should initialize them in a loop, then append all of the objects to a list. Then, in this case, all you'd have to do is iterate over the list and call the .clicked.connect() method on each object.
radio_buttons = list()
for i in range(1, 5):
temp_radio_button = QRadioButton("Radio Button")
radio_buttons.append(temp_radio_button)
....
for radio_button in radio_buttons:
radio_button.clicked.connect(self.fun)
I know it does not apply to this case, but for those who use QtDesigner and need to access the widgets later, this can be an option:
def get_all_objects(self,label,window,widget_class):
for widget in filter(lambda entry: match(label,entry.objectName()),window.findChildren(widget_class)):
yield widget
for widget in self.get_all_objects('radiobutton',MainWindow,QtWidgets.QRadioButton):
widget.clicked.connect(self.fun)
Note:MainWindow can be replaced by a page on a StackedWidget if all your target widgets are in the same page.

How to test window scroll event with vue-testing-library?

I want to trigger user interaction for window scroll to change the isScrolled data so that the div class can change. Right now I just test the component when the isScrolled data is changed. Is this the right way to test?
This is acceptable way to test. However, instead of directly setting the isScrolled to true, call the method using the vm as (ensure that you have mocked the document.documentElement.topScroll to something other than zero):
wrapper.vm.onScrollHandler();
Finally, if you need to really test for scroll event, you can manually fire one using the dispatchEvent() method as following:
window.dispatchEvent(new CustomEvent('scroll', { detail: 'anything' }));
You use this and then run your assertion.
On a side note, instead of pictures of your code, paste actual snippets so that they are easy to copy and paste. And, you onScrollHandler can be shortened to:
onScrollHandler() {
this.isScrolled = document.documentElement.topScroll > 0;
}

How to automate a splitButton using Rautomation ( adapter msuia)

I am trying to automate IE11 notification bar ( while downloading file) using Rautomation. Using MSUIA adapter I am able to catch the the save button. But I want to use Save As to supply the file location and name. But I cannot do that.
When seeing with UIspy I see that there is a splitbutton with name "Save". This splitbutton has another child splitbutton with name "" ( which is basically the down arrow) - I am not able to get to this control.
iemainwindow_local = RAutomation::Window.new(:class=>"IEFrame" , :adapter => :ms_uia )
ienotificationbar_frame = iemainwindow_local.child(:class=>"Frame Notification Bar")
ienotificationbar = ienotificationbar_frame.child(:class=>"DirectUIHWND")
if ienotificationbar.exists?
ienotificationbar.activate
sleep 1
mycontrol = ienotificationbar.control(:value =>"Save")
mycontrol2= mycontrol.control(:children_only => true)
mycontrol2.exist?
mycontrol.click
end
Getting error at this line mycontrol2= mycontrol.control(:children_only => true)
undefined method `control' for #<RAutomation::Adapter::MsUia::Control:0x4108e60>
Any idea how to get over this block?
I understand that there should be a menu and menuitems associated with the splitButton and when I click on down arrow besides Save, at UISpy I see that menu/ menu item is getting created directly under Desktop window ( though the processID is same ) - how to catch the menuitem Save as?
The Problem
Unfortunately, the :ms_uia adapter for RAutomation is not able to do this in its current form. I know this because I have written a lot of the UIA adapter for it :) The problem is that the current API doesn't allow you to really walk the tree like that (as you found out) because the Control class doesn't have a #control method. If the "Save" button had a native window handle, you'd be able to do this:
ieframe = RAutomation::Window.new(class: 'IEFrame')
save = RAutomation::Window.new(hwnd: ieframe.control(value: 'Save').hwnd)
save.control(index: 0)
Since it does not, unfortunately there isn't a reliable way to get down to it that I am aware of since it doesn't have any identifying properties about it (other than being a child of the "Save" button).
Alternative
I've written another gem called uia, which acts as a low-level wrapper around UI Automation and allows you to work more closely with UI Automation and interact with it how you see it within tools like UI Spy. Eventually, I will use this gem in RAutomation but have not had the time yet. To get down to the "Save As..." split button control in your circumstance, you can do this:
ieframe = UIA.find_element(title: /Thanks for downloading/)
save_as = ieframe.find(name: 'Save').find(control_type: :split_button)
save_as.as(:invoke).invoke
The save_as.as(:invoke) will treat the found "Save As" Element as something that implements the Invoke pattern, and then you can call the #invoke method to get the menu to pop.
Hope this helps!

NSTokenField click completion list item

I have an NSTokenField in my application and I implemented the tokenField:completionsForSubstring:indexOfToken:indexOfSelectedItem: method in which I specify the receiver’s tokenizing character set with the setTokenizingCharacterSet: method:
def tokenField(tokenField, completionsForSubstring:substring, indexOfToken:tokenIndex, indexOfSelectedItem:selectedIndex)
tokenField.setTokenizingCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet)
end
It works as expected when I click on the space bar or the enter button. I would also like to have the same behavior when I click on one of the item of the completion list with my mouse.
How is that possible?
Thanks for your help.
I don't know if it's possible to have this behaviour with a NSTokenField.
But you should take a look at MTTokenField which do what you want out of the box.
To do so, you will have to:
1.Create an Xcode Project as a Static Library(Do not enable ARC).
2.Save your project to vendor/MTTokenField
3.Drag and drop all the files of MTTokenField located in the subdirectory 'MTTokenField' to your new XCode project. Choose to copy the files.
4.Add this to your rakefile in order to compile and link the library with your Rubymotion project.
app.vendor_project("vendor/MTTokenField/", :xcode, :xcodeproj => "MTTokenField.xcodeproj", :target => "MTTokenField", :products => ["libMTTokenField.a"], :headers_dir => "MTTokenField")
5.In Interface Builder change the class of your NSTokenField to NSTextField and then set its custom class to MTTokenField, and also change the custom class of the cell: MTTokenFieldCell instead of NSTextFieldCell.
6.Then you have to set the delegate of your MTTokenField to a class which has to respond to :
def tokenField(tokenField, completionsForSubstring: substring )
# your have to return an array containing your results matching substring.
end
And that's it. It should work.
Hope it helps !
I found another solution using NSTokenField instead of MTTokenField.
In the delegate of my NSTokenField, I used the controlTextDidChange method of NSControl which is called anytime I write a character in my token field. In this method I check if there is a NSLeftMouseUp event which is triggered, and if it's the case, I simulate a click on Return. And that's it.
def controlTextDidChange(aNotification)
application = NSApplication.sharedApplication
event = application.currentEvent
if event.type == NSLeftMouseUp
e1 = CGEventCreateKeyboardEvent(nil, 0x24, true)
CGEventPost(KCGSessionEventTap, e1)
end
end
There is one more thing to do to make it properly work: the issue here is that if I have a completion list with 3 items for instance, one of them will be selected by default, let's say the first one. In this case, the solution will work as expected if I click the second or the third item but I will have to double click the first item to make it work.
To fix this, turn off the autocompletion and only display the suggestion box, i.e add this line to the tokenField:completionsForSubstring:indexOfToken:indexOfSelectedItem: method:
selectedIndex[0] = -1