I would like to create a unit test (with JUnit 5) for a controller which consists only of a ListView. The use case I want to test is when an item is selected/clicked-on the correct callback is invoked.
In my unit test I am doing the following:
myController.theListUnderTesting.selectionModel.select(i-th element)
But the selectedItemProperty listener is never invoked. This obviously works without problems when launching and using the application normally.
I've seen there are libraries such as TestFX that aid in testing but I don't like it because it launches the application and simulates the user interaction on screen.
Is there anyway to test such behavior without having to run the application and simulate the interaction?
What I ended up doing was:
Node nodeToClick = myController.getTheListUnderTesting().getChildrenUnmodifiable()[123];
MouseEvent mouseEvent =
new MouseEvent(
MouseEvent.MOUSE_CLICKED,
0.0,
0.0,
0.0,
0.0, MouseButton.PRIMARY, 1, false, false, false, false, false, false, false, false, false, true,
new PickResult(nodeToClick, 0.0, 0.0)
);
MouseEvent.fireEvent(nodeToClick, mouseEvent);
Related
During Xcode UI test, I found my custom view's (MenuViewButton) is un-hittable in a test, I could found it but cannot touch it. In debug , when I po isHittable in console, it returns false. However I'm not sure if this is the correct behavior.
Per this thread XCUIElement exists, but is not hittable said, isHittable is default false for custom view element, and default true for UIKit standard view. But I don't know if it is the same behavior in SwiftUI.
Since the way someView.isAccessibilityElement = true is not possible in SwiftUI. My question is how could I let my custom view became hittable? Then it could be tapped in a test.
private var aView: some View {
MenuViewButton(
image: Image("an image name"),
text: Text("a string")
)
.accessibility(identifier: "xxx name")
}
I also use force tap with coordinate in case tap() is not working, but to give a offset of normalizedOffset didn't fix the problem in all places, it means some un-hittable element could be tapped, that is great but some others still not.
So may I know where normalizedOffset is start, from the middle of frame to give the offset or the top left?
func forceTapElement(timeout: TimeInterval) {
if !self.waitForExistence(timeout: timeout) {
return
}
if self.isHittable {
self.tap()
} else {
let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.0))
coordinate.tap()
}
}
Add https://github.com/devexperts/screenobject as a dependency
Use .tapUnhittable() instead of .tap() for this particular view. It gets the coordinates and taps using them
I have the same situation and am looking for answers. What has worked for me was to use:
let coordinate = element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
Still this seems like workaround for a real problem. One other strange thing is that i do not have this problem on iOS15.0 simulator, only for later versions. Currently trying with iOS15.2
One more thing I've tried is to add
.accessibilityElement(children: .combine)
and specificly telling it's a button with
.accessibility(addTraits: .isButton)
But this doesn't solve the problem.
Seems that isAccessibilityElement would be an answer here, but SwiftUI doesn't seem to have such.
Does node-notifier support allowing the notification stay on the action center until dismissed by user? Timeout and wait attribute is currently not working
it's on depends on os. for mac its provides timeout and wait but for windows, it's not supported by windows.
Here are all notification options :
const NotificationCenter = require('node-notifier').NotificationCenter;
var notifier = new NotificationCenter({
withFallback: false, // Use Growl Fallback if <= 10.8
customPath: undefined // Relative/Absolute path to binary if you want to use your own fork of terminal-notifier
});
notifier.notify(
{
title: title,
subtitle: subtitle,
message: message,
sound: false, // Case Sensitive string for location of sound file, or use one of macOS' native sounds (see below)
icon: 'Terminal Icon', // Absolute Path to Triggering Icon
contentImage: undefined, // Absolute Path to Attached Image (Content Image)
open: undefined, // URL to open on Click
wait: false, // Wait for User Action against Notification or times out. Same as timeout = 5 seconds
// New in latest version. See `example/macInput.js` for usage
timeout: 5, // Takes precedence over wait if both are defined.
closeLabel: undefined, // String. Label for cancel button
actions: undefined, // String | Array<String>. Action label or list of labels in case of dropdown
dropdownLabel: undefined, // String. Label to be used if multiple actions
reply: false // Boolean. If notification should take input. Value passed as third argument in callback and event emitter.
},
function (error, response, metadata) {
console.log(response, metadata);
}
);
I'm trying to test if an image has been set by an imageView extension. I have the test passing when trying to load in from an http url. Now I want to test if the image gets set from an https url.
My current code is:
func test_DateImageLoadedFromHTTPSURL() {
let expected = expectation(description: "Image from https did load")
let viewer = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 250))
viewer.imageFromServerURL(urlString: "https://dummyimage.com/300x250/000/fff.png")
if viewer.image != nil {
expected.fulfill()
} else {
XCTFail()
}
waitForExpectations(timeout: 3.0, handler: nil)
}
It should work, unless I'm not seeing it.
Thanks
The order that you are doing things here will never work. Your method is asynchronous here so the order of things happening will be something like...
Run the method to get the image
Check the imageView.image is not nil (it is still nil at this point)
Wait for expectations
Download of image finished
Set the image
The method is asynchronous so the order of things happening changes.
You need to add some sort of completion to your method so that you can check the image is not nil AFTER it has been set and the completion been called.
Like...
viewer.imageFromServerURL(urlString: "https://dummyimage.com/300x250/000/fff.png") {
// image has finished loading here...
// check the image is not nil
}
Of course, you will need to update the actual method so that it will accept a closure and run the closure once the image is loaded.
I am currently writing unit tests for my React + MaterialUi application.
In my application I have a Dialog. I want to make sure depending on what button pressed on the dialog:
<FlatButton
label="Cancel"
secondary={true}
onTouchTap={this._cancelDialog.bind(this)}
/>
<FlatButton
label="Submit"
primary={true}
onTouchTap={this._confirmDialog.bind(this)}
/>
that the internal state changes accordingly.
Unfortunately i cannot get ahold of the dialog content using
TestUtils.scryRenderedComponentsWithType(FlatButton)
or
scryRenderedComponentsWithTag("button")
and so on.
Any ideas on how that flow can be tested?
Update 1
So I can get the Dialog instance by calling TestUtils.scryRenderedComponentsWithType(Dialog). But I can not get the dialogs content. DOM wise the content does not render inside the view itself. Its rendered in a new created node on document level (div). So i tried this:
let cancelButton = window.document.getElementsByTagName("button")[0];
Simulate.click(cancelButton);
cancelButton in the case above is the correct DOM element. Simulate.click however does not trigger the components click function.
regards
Jonas
just ran into the same problem. I looked into the source code, and the Dialog component's render method actually creates an instance of the component RenderToLayer. this component behaves as a portal and breaks react's DOM tree by returning null in its' render function and instead appending directly to the body.
Luckily, the RenderToLayer component accepts the prop render, which essentially allows the component to pass to the portal a function to be called when it is in a render cycle. This means that we can actually manually trigger this event ourselves. It's not perfect, i admit, but after a few days of poking around trying to find a solution for this hack i am throwing in the towel and writing my tests like this:
var component = TestUtils.renderIntoDocument(<UserInteractions.signupDialog show={true}/>)
var dialog = TestUtils.renderIntoDocument(component.refs.dialog.renderLayer())
var node = React.findDOMNode(dialog)
and here is what my UserInteractions.signupDialog looks like:
exports.signupDialog = React.createClass({
...
render: function() {
var self = this;
return (
<div>
<Dialog
ref='dialog'
title="Signup"
modal={false}
actions={[
<Button
label="Cancel"
secondary={true}
onTouchTap={self.__handleClose}
/>,
<Button
label="Submit"
primary={true}
keyboardFocused={true}
onTouchTap={self.__handleClose}
/>
]}
open={self.props.show}
onRequestClose={self.__handleClose}
>
<div className='tester'>ham</div>
<TextField id='tmp-email-input' hintText='email' type='text'/>
</Dialog>
</div>
)
}
})
Now i can make assertions against the child components rendered in the dialog box, and can even make assertions about events bound to my original component, as their relationship is maintained.
I definitely recommend setting up a debugger in your testing stack if you are going to continue using material ui. Theres not a lot of help for things like this. Heres what my debug script looks like:
// package.json
{
...
"scripts": {
"test": "mocha --compilers .:./test/utils/compiler.js test/**/*.spec.js",
"debug": "mocha debug --compilers .:./test/utils/compiler.js test/**/*.spec.js"
}
}
and now you can use npm test to run mocha tests, and npm run debug to enter debugger. Once in the debugger, it will immediately pause and wait for you to enter breakpoints. At this juncture, enter c to continue. Now you can place debugger; statements anywhere in your code to generate a breakpoint which the debugger will respond to. Once it has located your breakpoint, it will pause and allow you to engage your code using local scope. At this point, enter repl to enter your code's local scope and access your local vars.
Perhaps you didnt need a debugger, but maybe someone else will find this helpful. Good luck, happy coding!
Solved it as follows:
/*
* I want to verify that when i click on cancel button my showModal state is set * to false
*/
//shallow render my component having Dialog
const wrapper= shallow(<MyComponent store={store} />).dive();
//Set showModal state to true
wrapper.setState({showModal:true});
//find out cancel button with id 'cancelBtn' object from actions and call onTouchTap to mimic button click
wrapper.find('Dialog').props().actions.find((elem)=>(elem.props.id=='cancelBtn')).props.onTouchTap();
//verify that the showModal state is set to false
expect(wrapper.state('showModal')).toBe(false);
I ran into the same issue and solve it like that :
const myMock = jest.genMockFunction();
const matcherComponent = TestUtils.renderIntoDocument(
<MatcherComponent onClickCancel={myMock} activAction/>
);
const raisedButton = TestUtils.findRenderedComponentWithType(
matcherComponent, RaisedButton);
TestUtils.Simulate.click(ReactDOM.findDOMNode(raisedButton).firstChild);
expect(myMock).toBeCalled();
It works fine for me. However I'm still struggling with Simulate.change
Solution by avocadojesus is excellent. But I have one addition. If you try to apply this solution and get an error:
ERROR: 'Warning: Failed context type: The context muiTheme is marked
as required in DialogInline, but its value is undefined.
You should modify his the code as follows:
var component = TestUtils.renderIntoDocument(
<MuiThemeProvider muiTheme={getMuiTheme()}>
<UserInteractions.signupDialog show={true}/>
</MuiThemeProvider>
);
var dialogComponent = TestUtils.findRenderedComponentWithType(component, UserInteractions.signupDialog);
var dialog = TestUtils.renderIntoDocument(
<MuiThemeProvider muiTheme={getMuiTheme()}>
{dialogComponent.refs.dialog.renderLayer()}
</MuiThemeProvider>
);
var node = React.findDOMNode(dialog);
Material UI fork the 2 enzyme methods. You need to use the createMount or the createShallow with dive option https://material-ui.com/guides/testing/#createmount-options-mount
I have a global singleton "Settings" which holds application settings. When I try to run the following code I get a QML CheckBox: Binding loop detected for property "checked":
CheckBox {
checked: Settings.someSetting
onCheckedChanged: {
Settings.someSetting = checked;
}
}
It is obvious why this error occurs, but how can I correctly implement this functionality without a binding loop? E.g. I want to save the current checked state of the checkbox in the settings singleton.
I am using Qt 5.4 and Qml Quick 2.
Regards,
Don't bind it. Because the check box does not fully depend on Setting.someSetting.
When a user clicked the checkbox, the CheckBox.checked is changed by itself. At the same time, the property binding is no longer valid. Settings.someSetting cannot modify the CheckBox after it is clicked by user. Therefore, the checked: Settings.someSetting binding is wrong.
If you want to assign an initial value to the check box when the component is ready, use Component.onCompleted to assign it:
CheckBox {
id: someSettingCheckBox
Component.onCompleted: checked = Settings.someSetting
onCheckedChanged: Settings.someSetting = checked;
}
If you are working on a more complex scenario, the Setting.someSetting may be changed by some other things during runtime and the state of the check box is required to be changed simultaneously. Catch onSomeSettingChanged signal and explicitly changed the check box. Submit the value of someSettingCheckBox to Settings only when the program/widget/dialog/xxx finished.
CheckBox { id: someSettingCheckBox }
//within the Settings, or Connection, or somewhere that can get the signal.
onSomeSettingChanged: someSettingCheckBox.checked = someSetting
I prefer this solution
// Within the model
Q_PROPERTY(bool someSetting READ getSomeSetting WRITE setSomeSetting NOTIFY someSettingChanged)
void SettingsModel::setSomeSetting(bool checkValue) {
if (m_checkValue != checkValue) {
m_checkValue = checkValue;
emit someSettingChanged();
}
}
// QML
CheckBox {
checked: Settings.someSetting
onCheckedChanged: Settings.someSetting = checked
}
The trick is you protect the emit with an if check in the model. This means you still get a binding loop but only a single one, not an infinite one. It stops when that if check returns false thereby not emitting to continue the loop. This solution is very clean, you do not get the warning, and yet you still get all the benefits of the binding.
I want to talk about the limitations of the other solutions presented
CheckBox {
Component.onCompleted: checked = Settings.someSetting
onCheckedChanged: Settings.someSetting = checked;
}
In this solution you lose your binding. It can only have a default setting on creation and be changed by the user. If you expand your program such that other things change the values in your model, this particular view will not have a way to reflect those changes.
Settings {
id: mySettings
onSomeSettingChanged: checkBox.checked = someSetting
}
CheckBox {
id: checkBox
onCheckedChanged: mySettings.someSetting = checked
}
This solution was mentioned to address these problems but never written out. It is functionally complete. Model changes are reflected, the user can change the data, and there are no binding loops because there are no bindings; only two discrete assignments. (x: y is a binding, x = y is an assignment)
There are a couple problems with this. The first is that I think its ugly and inelegant, but that is arguably subjective. It seems fine here but if you have a model representing 10 things in this view, this turns into signal spaghetti. The bigger problem is that it does not work well with delegates because they only exist on demand.
Example:
MyModel {
id: myModel
// How are you going to set the check box of a specific delegate when
// the model is changed from here?
}
ListView {
id: listView
model: myModel.namesAndChecks
delegate: CheckDelegate {
id: checkDelegate
text: modelData.name
onCheckStateChanged: modelData.checkStatus = checked
}
}
You can actually do it. I've made up custom QML signals and connections to do it, but the code complexity makes me want to hurl, and even worse you could possibly be forcing creation of a delegate when it is not necessary.
If you don't want to make a binding loop - don't make a binding, use a proxy variable, for example. Other simple solution can be to check the value:
CheckBox {
checked: Settings.someSetting
onCheckedChanged: {
if (checked !== Settings.someSetting) {
Settings.someSetting = checked;
}
}
}
You can also make two-way binding to resolve this issue:
CheckBox {
id: checkBox
Binding { target: checkBox; property: "checked"; value: Settings.someSetting }
Binding { target: Settings; property: "someSetting"; value: checkBox.checked }
}
Sometimes it is useful to separate input and output values in control. In this case control always displays real value and it can also show a delay to the user.
CheckBox {
checked: Settings.someSetting
onClicked: Settings.someSetting = !checked
}