0

Trying to write a unit test for my ember controller. It just changes a property and then opens a bootstrap modal. Having difficulty figuring out how to test that the modal actually gets opened. Not sure if this even belongs in a unit test or integration test. If it's not in my unit test it seems like it will be difficult to determine code coverage later down the line. Bootstrap version: 3.3.1, ember-cli version 0.1.5, node 0.10.33. Here is what I've tried to no avail:

1.

test('loginClick() opens modal', function(){
  var controller = this.subject();
  $('#login-modal').on('show.bs.modal', function(){
    equal(true, true, "the show.bs.modal event fired");
  });

  controller.send('loginClick', 'anything');
});

no assertion error

2.

test('loginClick() opens modal', function(){
  var controller = this.subject();
  andThen(function(){
    controller.send('loginClick', 'anything');
    stop();

    Ember.run.later(function(){
        start();
        equal($('#login-modal').hasClass('in'), true, "has the 'in' class");
    }, 500);
  });
});

andThen is not defined

Here is the controller:

loginClick: function(param){
  this.set('provider', param);//facebook or google

  $('#login-modal')
    .modal();
}

Any other suggestions or best practices on how to test this kind of thing will be appreciated.

p.s. Also tried adding this before click:

$.support.transition = false;

per someone's suggestion, but it does not disable the modal transition.

carter
  • 5,074
  • 4
  • 31
  • 40
  • What version of bootstrap are you using? – slindberg Jan 15 '15 at 21:43
  • Which test did you try `$.support.transition = false` with? Upon initial inspection, it should work with the first. I would just set it in your `tests/test-helper.js` file to turn it off for all bootstrap widgets. Also, `andThen` is only defined after calling `app.injectTestHelpers()`, typically done in the `startApp` helper (which is done when writing integration/acceptance tests). – slindberg Jan 15 '15 at 23:12

2 Answers2

4

I had the same problem. I’m not sure this is the best solution, but I solved it by registering an async test helper before calling App.injectTestHelpers():

Ember.Test.registerAsyncHelper 'waitForModalOpen', (app, modal) ->
  # If using QUnit < 1.16, you need to add stop().
  #stop()
  Ember.Test.promise (resolve, reject) ->
    modal.on 'shown.bs.modal', ->
      resolve()
      # If using QUnit < 1.16, you need to add start().
      #start()

I then call it after clicking the button and before the assertions:

modal = find '#testModal'
click '#openModal'
waitForModalOpen modal
andThen ->
  strictEqual modal.attr('aria-hidden'), 'false', 'modal should be visible'
  strictEqual modal.hasClass('in'), true, 'modal should have .in class'

Here is a JS Bin testcase. QUnit 1.16 supports returning promises from tests, so with this version, calling stop() and start() is not needed anymore: QUnit will wait for the andThen() promise to resolve.

Edit: Use in ember-cli

The ember-cli documentation has a section about writing your own test helpers.

Create the helper as /tests/helpers/wait-for-modal-open.js:

import Ember from "ember";

export default Ember.Test.registerAsyncHelper('waitForModalOpen', function(app, modal) {
  return Ember.Test.promise(function(resolve, reject) {
    return modal.on('shown.bs.modal', function() {
      return resolve();
    });
  });
});

Then, add this line in /tests/helpers/start-app.js:

import waitForModalOpen from './wait-for-modal-open';

You also have to add "waitForModalOpen" in the "predef" array in /tests/.jshintrc to avoid JSHint errors.

Finally, create the test as a file in /tests/integration:

import Ember from "ember";
import { test } from 'ember-qunit';
import startApp from '../helpers/start-app';
var App;

module('Bootstrap Modal Open', {
  setup: function() {
    App = startApp();
    return visit('/');
  },
  teardown: function() {
    Ember.run(App, App.destroy);
  }
});

test('clicking the button should open a modal', function() {
  var modal;
  modal = find('#testModal');
  click('#openModal');
  waitForModalOpen(modal);
  return andThen(function() {
    strictEqual(modal.attr('aria-hidden'), 'false', 'modal should be visible');
    return strictEqual(modal.hasClass('in'), true, 'modal should have .in class');
  });
});
  • Thanks. After I convert it into javascript I'll see if it works. I imagine I should be able to figure out the js solution by looking at a coffeescript solution. – carter Feb 02 '15 at 18:20
  • I found a coffee script to js converter but can't figure out how to get the helper available in my test. It sucks that all documentation is NOT for ember-cli. – carter Feb 03 '15 at 00:49
  • I just dived into ember-cli for the first time to try this out ;). The ember-cli documentation has a section about [writing your own test helpers](http://www.ember-cli.com/#writing-your-own-test-helpers). I edited my answer with the explanation. – hugues de keyzer Feb 03 '15 at 09:08
  • FYI: In the start-app.js file when adding a comment directly to end of an import statement without space causes an error. e.g. `import waitForModalOpen from './wait-for-modal-open';//my comment` will cause an error while `import waitForModalOpen from './wait-for-modal-open'; //my comment` is fine. – carter Feb 03 '15 at 18:02
  • The only problem I see is that you can't run anything after that. e.g. Now fill in the login form and submit it to see if the page redirects properly. – carter Feb 03 '15 at 18:19
  • You probably would want the test to wait for the modal to close (assuming you close it when submitting the form). For this you need another async helper. It’s the same as `waitForModalOpen()`, but using the `hidden.bs.modal` event instead. – hugues de keyzer Feb 05 '15 at 09:10
  • About the documentation, Erik Bryn says in [his talk about Ember 1.10](http://youtu.be/tFGFR8JRq-A?t=29m37s) that the whole documentation will soon be updated for Ember CLI. – hugues de keyzer Feb 06 '15 at 12:33
  • I have a strange error, `Uncaught Error: Called stop() outside of a test context` any hint? – Fed03 Feb 06 '15 at 17:28
  • @Fed03: This is probably because the execution of the test function has already finished, and some code is calling `stop()`. Which version of QUnit are you using? Only QUnit >= 1.16 supports returning promises from tests. The code should work with earlier versions by adding the `stop()` and `start()` calls. Also, be sure to use `Ember.Test.promise()` and not `new Ember.RSVP.Promise()` or `new Promise()`, as these would not be properly chained with `click()` and `andThen()`. – hugues de keyzer Feb 07 '15 at 12:51
  • @HuguesDeKeyzer I'm on latest ember-cli so I guess I'm using qunit >= 1.* – Fed03 Feb 08 '15 at 15:12
  • @Fed03: Did you manually add the stop() call? Could you provide a JSBin reproducing this error? Please post a new question about this, so other users with the same problem will find it more easily. – hugues de keyzer Feb 09 '15 at 12:42
  • @HuguesDeKeyzer sure here it is https://gist.github.com/Fed03/38a3d75d9549a8c0f64b – Fed03 Feb 09 '15 at 16:01
  • @Fed03: I could not reproduce the same error with your code, but for me it fails with the error `Expected 3 assertions, but 1 were run`. For it to work, I had either to add the `stop()` and `start()` calls in the helper, or add `return` before the `andThen()` call (to return the return value of `andThen()`). – hugues de keyzer Feb 12 '15 at 10:35
  • @HuguesDeKeyzer I think that we're missing something....If I find a solution i'll let you know – Fed03 Feb 12 '15 at 10:44
  • @HuguesDeKeyzer, this solution is not working to me with 1.10. Therefore, need to use registerWaiter instead. In my case, the `andThen` helper did not stop for the asyncHelper promise resolution – ppcano Mar 31 '15 at 11:14
1

Another generic solution is to create an async helper waitUntil which waits for a html selection to appear.

Ember.Test.registerAsyncHelper('waitUntil', function(app, selector, callback) {

  var waiter = function() {
    return $(selector).length > 0;
  };

  Ember.Test.registerWaiter(waiter);
  var promise = app.testHelpers.wait();

  promise.then(function() {
    Ember.Test.unregisterWaiter(waiter);
  });

  // it will be resolved when the pending events have been processed
  // (routing loads, ajax requests, run loops and waiters)
  return promise;
});

Then, waitUntil can be used with other async helpers as:

waitUntil('#my-button');
click('#my-button');

or before any sync helper as:

waitUntil('#my-modal.modal').then(function() {
  var el = find('#my-modal.modal');
  assert.ok(el.length > 0, 'modal was open');

  ....
});

As this example shows registerWaiter and registerAsyncHelper can be used in conjunction to solve similar use cases.

ppcano
  • 2,831
  • 1
  • 24
  • 19