How to make widget test wait until Bloc has updated the state? - testing

I have a EmailScreen (stateful widget) that has a text input, and a button. The button is only enabled when a valid email is input.
I'm using Bloc, and my screen has InitialEmailState and ValidEmailInputState, and it works fine when I run the app.
In my widget test, the second expectation is failing before bloc has a chance to update the state:
testWidgets('when valid email is input, button is enabled', (tester) async {
const validEmail = 'email#provider.com';
emailBloc.listen((event) {
print('NEW EVENT: ' + event.toString());
});
await bootUpWidget(tester, emailScreen);
final BottomButton button = tester.widget(
find.widgetWithText(BottomButton, 'CONTINUE'));
expect(button.enabled, isFalse);
await tester.enterText(find.byType(TextInputScreen), validEmail);
await tester.pumpAndSettle();
expect(button.enabled, isTrue);
});
And here's the output I'm getting:
NEW EVENT: InitialEmailState
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test:
Expected: true
Actual: <false>
...
The test description was:
when valid email is input, button is enabled
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: when valid email is input, button is enabled
NEW EVENT: InputValidEmailState
✖ when valid email is input, button is enabled
Exited (1)
As you can see, it prints the initial state, fails the second expectation, and then prints the expected state.
Thanks in advance :)
== UPDATE ==
We managed to get this to work by adding LiveTestWidgetsFlutterBinding(); to the start of our main. But it doesn't feel like a good solution.

Encountered the same issue with BLoC driven UI widgets, I found the solution is pretty simple: use expectLater() rather than expect() to wait for BLoC state update:
testWidgets('test widgets driven by BLoC', (tester) async {
await tester.pumpWidget(yourWidget);
await tester.tap(loginButton); // Do something like tapping a button, entering text
await tester.pumpAndSettle();
await expectLater(findSomething, findsOneWidget); // IMPRTANT: use `expectLater` to wait for BLoC state update
expect(findSomethingElse, findsOneWidget); // Subsequently you can use normal version `expect` until next `tester.pumpAndSettle()`
}
I put breakpoints into the BLoC to figure out what's the problem, it turns out that without using expectLater, the normal expect is being evaluated before the BLoC stream emits a new state. That is to say BLoC does emit a new state, but at that time the test case already runs to the end.
By using expectLater, it is being evaluated after the new state is emitted.

Related

Testcafe .parent() of Selector sporadically fails before timeout

I have a TestCafe test that checks if a Selector's parent exists and somehow it fails every other time. Here's the relevant code:
logWithTimestamp("Starts..."); // Prints "[2020-12-23T12:02:04.476Z] Starts..."
let state = await Selector('#indberetningsflow-knap-trin-stamdata', {timeout: 30000}).parent().exists;
logWithTimestamp(`State: ${state}`); // Prints "[2020-12-23T12:02:04.618Z] State: false"
await t.expect(state).ok() // Sometimes fails
As you can see, it fails after less than 200ms, even though the timeout is set explicitly to 30000. Now, I have an idea that maybe it fails when the selector is found, but the parent is not yet loaded. If this is true, why does TestCafe not wait for the parent to appear, and what can I do about it?
EDIT
I performed another experiment, and either there is something wrong with TestCafe or I have not understood something fundamental, but how can this fail after just 30ms?
logMedTidsstempel("Starts..."); // Prints "[2020-12-23T12:42:15.041Z] Starts..."
let state = await Selector('#indberetningsflow-knap-trin-stamdata', {timeout: 30000}).exists;
logMedTidsstempel("Found child."); // Prints "[2020-12-23T12:42:15.072Z] Found child."
await t.expect(state).ok(); // <- fails :(
To make your code example work, do the following:
Remove the await keyword from Selector
Remove the timeout option from Selector and pass it to the assertion method
...
let state = Selector('#indberetningsflow-knap-trin-stamdata').exists;
await t.expect(state).ok({timeout: 30000});
...
You may also wish to refer to the following help topic: https://devexpress.github.io/testcafe/documentation/guides/basic-guides/select-page-elements.html#selector-timeout

Uncaught TypeError: Cannot read property 'host' of undefined in Durandal when showing/closing modal dialog

Using Durandal 2.0.1 (can't update to 2.1.0 yet due to restrictions on project development) and I have an intermittent issue with the error shown in this question title.
All I'm doing is defining a custom dialog box then showing it:
var pleaseWaitModal = new modalBusy();
dialog.show(pleaseWaitModal);
And when my ajax call is finished I do:
dialog.close(pleaseWaitModal);
...and then display another modal with the results of my ajax call.
This all works perfectly IF the ajax call takes half a second to a second. If it's a quicker call then I get Uncaught TypeError: Cannot read property 'host' of undefined in my console window. The box still closes, it's just that I get a panicky project manager asking what the red error is for...
Is this purely because I'm trying to run "dialog.close()" before "dialog.show()" has properly completed in some circumstances?
The sequence of events is basically:
*user instigates action requiring a detailed modal dialog to appear with data in it
*as it takes several seconds to populate on some occasions, an interim modal dialog is shown with "please wait" in it
*once the ajax request is complete, the "pleasewait" modal is closed and the "detail" modal is shown
*so a bit like:
var pleaseWaitModal = new modalBusy();
dialog.show(pleaseWaitModal);
//set up deferred calls for ajax data and call ...
var deferredAjax = callDataFunction(myparams...);
return deferredAjax.then(function(result) {
dialog.close(pleaseWaitModal);
var detailModal = new detailModal();
detailModal.show(result);
});
So I don't think I can achieve this using the promise on the dialog.show(pleaseWaitModal) call, can I?
Are you using the promise that is returned from the dialog.close function to open your new modal? You might try this:
From your initial dialog:
dialog.show(new modal()).then(function(responseData) {
dialog.show(new pleaseWaitModal(responseData));
});
I think the problem you're running into is async timing related, which is why using the deferred works so well.
EDIT: Related to my comment below, you might look at using only one modal, and putting a loading indicator inside of it, like so:
view.html
<div data-bind="visible: isLoading">
<h1>Please wait...</h1>
<i class="icon-spin icon-spinner icon-4x"></i>
</div>
modalViewModel.js
var vm = {
isLoading: ko.observable(true)
};
vm.activate = function() {
makeAjaxCall().then(function(data) {
vm.isLoading(false);
**Do whatever you need for your ajax return**
return true;
});
});
I think that should work for what you need as an alternative.

Backbone: how to test preventDefault to be called without testing directly the callback

Let's say we have a simple Backbone View, like this:
class MyView extends Backbone.View
events:
'click .save': 'onSave'
onSave: (event) ->
event.preventDefault()
# do something interesting
I want to test that event.preventDefault() gets called when I click on my element with the .save class.
I could test the implementation of my callback function, pretty much like this (Mocha + Sinon.js):
it 'prevents default submission', ->
myView.onSave()
myView.args[0][0].preventDefault.called.should.be.true
I don't think it's working but this is only to get the idea; writing the proper code, this works. My problem here is that this way I'm testing the implementation and not the functionality.
So, my question really is: how can I verify , supposing to trigger a click event on my .save element?
it 'prevents default submission', ->
myView.$('.save').click()
# assertion here ??
Thanks as always :)
Try adding a listener on the view's $el, then triggering click on .save, then verify the event hasn't bubbled up to the view's element.
var view = new MyView();
var called = false;
function callback() { called = true; }
view.render();
// Attach a listener on the view's element
view.$el.on('click', callback);
// Test
view.$('.save').trigger('click');
// Verify
expect(called).toBeFalsy();
So you want to test that preventDefault is called when a click event is generated, correct?
Couldn't you do something like (in JavaScript. I'll leave the CoffeeScript as an exercise ;)):
var preventDefaultSpy;
before(function() {
preventDefaultSpy = sinon.spy(Event.prototype, 'preventDefault');
});
after(function() {
preventDefaultSpy.restore();
});
it('should call "preventDefault"', function() {
myView.$('.save').click();
expect(preventDefaultSpy.callCount).to.equal(1);
});
You might want to call preventDefaultSpy.reset() just before creating the click event so the call count is not affected by other things going on.
I haven't tested it, but I believe it would work.
edit: in other words, since my answer is not that different from a part of your question: I think your first approach is ok. By spying on Event.prototype you don't call myView so it's acting more as a black box, which might alleviate some of your concerns.

How to trigger <enter> in an input in Angular scenario test?

I'm writing tests with Angular Scenario test runner. Within a traditional form, I can enter text into an input, but I need to press enter to execute the query and there is no button to click on. Surely there is some easy way to do this, but I do not know what it is.
input('query').enter('foo bar');
// ... now what?
I tried to simulate a keypress with JQuery, but as this answer indicates JQuery is not loaded in the e2e scenarios scope. So I followed his advice (as well as that of this answer) to simulate the keypress:
element('#search_input').query(function(el, done){
var press = document.createEvent('keypress');
press.which = 13;
press.trigger(evt);
done();
});
But to this Angular replies:
NotSupportedError: DOM Exception 9
Error: The implementation did not support the requested type of object or operation.
Update
I realized that a very easy workaround is to include a hidden submit input in my form:
<input id="search-submit" type="submit" style="display:none;">
Then in the scenario: element('#search-submit').click(); does what is needed.
For a purer solution which doesn't involve modifying the HTML for the sake of testing, #florian-f's answer (as well as this one) provides access to jQuery within the DSL via:
var $ = $window.$;
which can be used there or passed to the callback. However, even with this access when triggering a press of enter I was not able to submit my form in the following manner:
$(selector).trigger($.Event('keypress', { which: 13 }));
This must be another issue all together. But I did find jQuery's submit function to do the trick:
$(#the_form).submit();
You can access to the app (runner in an iframe) instance of jQuery :
angular.scenario.dsl('appElement', function() {
return function(selector, fn) {
return this.addFutureAction('element ' + selector, function($window, $document, done) {
fn.call(this, $window.angular.element(selector));
done();
});
};
});
Then you can call the trigger method of jQuery in your test :
appElement('yourSelector', function(elm) {
elm.trigger('enter');//or keypress
});
There is also another possibility to trigger a key event. While your first approach
element('#search_input').query(function(el, done){
var press = document.createEvent('keypress');
press.which = 13;
press.trigger(evt);
done();
});
will be blocked by angular, this one
element(<selector>).query(function($el, done) {
var event = new CustomEvent('keyup');
event.keyCode = 13;
$el.val(2);
$el.get(0).dispatchEvent(event);
done();
});
will pass and trigger a keyup event on the element specified by the selector (keyCode = 13 = Enter Key). See https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent for further information.

BusyIndicator not working

Worklight busyindicator not working properly.My isssue is i'm using multipage.On page change i call adapter for webservice and call busy indicator so that it show work in progress while fetching.but what happen is page change and indicator show and hide quickly but adpater still in fetching phase and after sometime data called successfully but during these working no busy indicator shows.
var busyIndicator = null;
function wlCommonInit(){
busyIndicator = new WL.BusyIndicator();
}
This is the code i call on page change.
busyIndicatorDemo();
var viewPath = "views/add_fund_transfer.html";
WL.Page.load(viewPath,
{
onComplete: function() {
PayAnyOne_Controller.GetBranches(GetBranchesProcedureName);
busyIndicator.hide();
}
});
function busyIndicatorDemo() {
busyIndicator.show();
setTimeout(15000);
}
its seems like busyindicator doesn't work with adpater when using in multipage.
Please give me the solution or the problem in my code.
Thanks.
It seems like the problem is in the flow of the code. you're running this code basically:
show busy indicator
load page
when page has finished loading: invoke procedure (async call), and hide busyindicator.
So this generates the behavior you've reported - the busyindicator is shown and quickly hidden once the page has finished loading, even though the service is still fetching data (in an async call)
moving the busyindicator.hide to the onSuccess of the invoke procedure should solve the problem (put it also in the onFailure ...)
Hope this helps