emberjs / ember.js

Ember.js - A JavaScript framework for creating ambitious web applications
https://emberjs.com
MIT License
22.45k stars 4.21k forks source link

Ember testing, wait for modal window #5494

Closed lrdevries closed 8 years ago

lrdevries commented 10 years ago

In my application i use bootbox (http://bootboxjs.com/) for confirming to delete some models. In my test i click on the button with an {{ action }}, in this action i open a bootbox. In the text i click the OK button on the bootbox.

But the andThen method does not wait for the bootbox to dissapear. The check if a model is removed is therefore always false. I tried to return an promise but the andThen helpen also doesn't wait for promisses.

Is there a way to let the test wait untill the promise of the bootbox is resolved, or the callback of the bootbox is finished?

Extra information: My template is:

        <button class="btn btn-default" {{action 'askDelete' target="view"}} data-toggle="tooltip" data-placement="right" {{translateAttr title="trips.remove"}}><i class="fa fa-trash-o"></i></button>

My View is:

    actions: {
        askDelete: function(){
            var self = this;
            self.$('td').slideUp('500').promise().then(function(){
                self.get('controller').send('delete');
            });
        }
    }

My controller:

    actions: {
        save: function(){
                    self.get('content').save().then(function(){
                    self.get('controllers.history').transitionToPrevious('trips.index');
                });

My test:

    click(".specific-row .action-buttons button");

    andThen(function() {
        // Check if the row is indeed removed from the table
        ok(find(".content-row table tr:contains('content-of-row')").length === 0, "deleted and removed from the table");
    });

But the latest ok is called before the save action in the controller is triggered, and therefore alwasy false.

lrdevries commented 10 years ago

Here you can find a jsfiddle example: http://jsfiddle.net/21af9puz/1/

The test fails because of the async call of the bootbox

lrdevries commented 10 years ago

I now use the cookbooks method for modals, that works: http://jsfiddle.net/21af9puz/14/ But this should be also a problem in other situations i think.

workmanw commented 10 years ago

Here you go, try this: http://jsfiddle.net/21af9puz/17/ In your case, the test was failing because you weren't actually click the link, right?

However, if it was an async issue I'd recommend using Ember.Test.registerWaiter.

workmanw commented 10 years ago

Oh yea, I see you found the outlets. Yea, the real problem with your case was the click doesn't look for thing outside of the ember app space. So if you're using a modal and not using an outlet, I'd recommend an andThen.

lrdevries commented 10 years ago

what is the biggest difference between click an $('jquery').click(); ? if i wrap the click() in an andThen, it also doesn't work.

fivetanley commented 10 years ago

If you use RSVP promises (Ember has these as Ember.RSVP.Promise), Ember can watch for them in testing mode and wait for the promises to resolve before other stuff happens.

I would try the following in your view code:

actions: {
        askDelete: function(){
            var self = this;
            return new Ember.RSVP.Promise(function(resolve, reject){
              self.$('td').slideUp('500').promise().then(function(){
                self.get('controller').send('delete');
                resolve();
              });
            });      
        }
    }
lrdevries commented 10 years ago

I tried that, but it didn't work. Probably because an action can only return a boolean. And therefore the promise do not reach the test Op 27 aug. 2014 00:13 schreef "Stanley Stuart" notifications@github.com:

If you use RSVP promises (Ember has these as Ember.RSVP.Promise), Ember can watch for them in testing mode and wait for the promises to resolve before other stuff happens.

I would try the following in your view code:

actions: { askDelete: function(){ var self = this; return new Ember.RSVP.Promise(function(resolve, reject){ self.$('td').slideUp('500').promise().then(function(){ self.get('controller').send('delete'); resolve(); }); }); } }

— Reply to this email directly or view it on GitHub https://github.com/emberjs/ember.js/issues/5494#issuecomment-53501017.

fivetanley commented 10 years ago

Well, the RSVP scheduler is supposed to do this for us since 1.6, regardless of if you return or not. @lrdevries you can work around it by making the promise available somewhere globally like http://jsfiddle.net/x2jk4vf2/1/

lrdevries commented 10 years ago

Thank you for the solution, but I think the global isn't the most beautiful solution. I will take a look at the RSVP scheduler, how I can let it work through that. Op 27 aug. 2014 00:19 schreef "Stanley Stuart" notifications@github.com:

Well, the RSVP scheduler is supposed to do this for us since 1.6, regardless of if you return or not. @lrdevries https://github.com/lrdevries you can work around it by making the promise available somewhere globally like http://jsfiddle.net/x2jk4vf2/1/

— Reply to this email directly or view it on GitHub https://github.com/emberjs/ember.js/issues/5494#issuecomment-53501699.

fivetanley commented 10 years ago

I think there may be some semantics in here that may be worth improving: https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/ext/rsvp.js#L26

Maybe we just want to always asyncStart() asyncEnd() if in Ember.testing? cc @stefanpenner

fivetanley commented 10 years ago

So i think the problem is that we are firing asyncEnd any time a promise is resolved during async...?

workmanw commented 10 years ago

No. I believe the problem is the click. His modal is outside of the application template. So the testing click handler doesn't seem to find it. If you look at this: http://jsfiddle.net/21af9puz/17/ You'll see it works without any promises or async helpers.

@lrdevries IIRC the click helper is design to only click things within your "application". Meaning anything that is a descendant of your application template. Using the andThen(function() { $('.btn-primary').click(); }) enabled it to search the entire document.

lrdevries commented 10 years ago

@workmanw ahh i see. i noticed that click($('.btn-primary')); also worked.

This lets the test wait for the click. But if you want to wait for the whole modal window, you have to wait for the promise.

workmanw commented 10 years ago

@lrdevries Just as a heads up, you cannot depend on click($('.btn-primary')) to always work. Each of the helpers in your test is executed without blocking (obviously, it's JS of course). So if any action you take might create an async operation (promises, deferred, ajax, animation, etc). The test will continue executing the helper calls (even if waiting for an AJAX) and the internal test suite will queue up each subsequent helper action.

In the case of click($('.btn-primary')), if a prior async operation had existed, $('.btn-primary') could have resolved before a prior operation had been completed (and potentially before '.btn-primary' was in the DOM). That's why you always wrap anything that's not an async helper with and andThen.

Make sense?

lrdevries commented 10 years ago

Yes it definitely make sense.

benkiefer commented 9 years ago

Sorry to wake this back up, but is there a best practice for this? We are seeing the same issue when launching a foundation modal from an action:

 actions: {
     openModal: function () {
           $("#some_modal_id").foundation("reveal", "open");
     }
 }
michaeldeitcher commented 9 years ago

I'm running into this issue as well @benkiefer

lrdevries commented 9 years ago

If i remember correctly i noticed a difference between click($(..)); and $(..).click();. And if i'm right i solved it with this.

benkiefer commented 9 years ago

$(..).click() works occasionally for me. I ended up solving it with the answer to this question:

http://stackoverflow.com/questions/25512168/async-call-in-ember-testing

My code looks like this:

 click("#modal-launch-btn");

 andThen(function () {
     waitFor(function () {
         assert.ok(!find("#myModal").is(":hidden"));
     });
 });

runAndWaitFor looks like this:

 function waitFor(callback, timeout) {
     timeout = timeout || 500;
     stop();
     Ember.run.later(function(){
          callback();
          start();
     }, timeout);
 }

Which basically just means I pause the test and take a nap. Not ideal, but at least I can test drive my modals now.

michaeldeitcher commented 9 years ago

Thanks @benkiefer

rwjblue commented 8 years ago

From the last few comments this issue looks to be resolved.