Ember 2.8: Test that a ember-bootstrap modal is open on page load - unit-testing

Since I'm not working with pure Bootstrap modals, I've been having trouble figuring out how to unit test for a modal that opens on page load. Here's the modal in question:
{{#bs-modal class="startModal" footer=false open=openModal title="Start Game" closedAction="closeModal" backdropClose=false closeButton=false}}
//modal content
{{/bs-modal}}
I tried adding a startModal class in hopes of somehow capturing it with find on my unit test
game-test.js
test('Initial modal shows up', function(assert) {
visit('/');
andThen(function () {
assert.equal(find('.startModal').length, 1);
});
});
This test passes, but it's not really what I'm looking for. I need to assert that the modal is actually shown and not just present.

Why don't you check for a class appended on the modal or css property change:
test('Initial modal shows up', function(assert) {
visit('/');
andThen(function () {
assert.equal(find('.startModal').hasClass('opened'), true);
// or
// assert.equal(find('.startModal').css('display'), 'block');
});
});

Related

How to test that an action is fired in a component integration test?

I have a very simple component and I'm trying to test that, when the button is clicked, the appropriate action is triggered. I've followed the docs as closely as I can, and also read several blog posts and SO questions covering the same material, and hence tried several subtly different ways of getting this to work but I just cannot get the test to pass. I've confirmed that it does actually work in the browser. What am I doing wrong?
add-thing.hbs:
{{#if displayForm}}
<form class="form-inline">...form content...</form>
{{else}}
<button {{action 'toggleForm'}} class="btn btn-default add">Add</button>
{{/if}}
add-thing.js:
import Ember from 'ember'
export default Ember.Component.extend({
displayForm: false,
actions: {
toggleForm () {
this.toggleProperty('displayForm')
}
}
})
add-thing-test.js:
import Ember from 'ember'
import { moduleForComponent, test } from 'ember-qunit'
import hbs from 'htmlbars-inline-precompile'
moduleForComponent('add-group', 'Integration | Component | add thing', {
integration: true
})
test('it toggles the form when the Add button is clicked', function (assert) {
assert.expect(1)
this.set('assertCalled', () => { assert.ok(true) })
// Have also tried this.on here instead of this.set
this.render(hbs`{{add-thing toggleForm=(action assertCalled)}}`)
// Have also tried passing in the action like {{add-thing toggleForm='assertCalled'}} as some blog posts suggest
Ember.run(() => document.querySelector('button.add').click())
// Have also tried just a this.$('button.add').click() here
})
Test output:
not ok 16 PhantomJS 2.1 - Integration | Component | add thing: it toggles the form when the Add button is clicked
---
actual: >
null
expected: >
null
stack: >
http://localhost:7357/assets/tests.js:221:24
exports#http://localhost:7357/assets/vendor.js:111:37
requireModule#http://localhost:7357/assets/vendor.js:32:25
require#http://localhost:7357/assets/test-support.js:20229:14
loadModules#http://localhost:7357/assets/test-support.js:20221:21
load#http://localhost:7357/assets/test-support.js:20251:33
http://localhost:7357/assets/test-support.js:7708:22
message: >
Expected 1 assertions, but 0 were run
Log: |
...
Ember: v2.14.0
It looks like you have two different things happening.
this.toggleProperty('displayForm')
that action will toggle displayForm from true to false, but it doesn't "bubble up" or go up anywhere. Clicking the button will fire it and then that is it.
Your test, on the other hand, is looking to see if clicking the button fires an action up to another level.
You can test this by checking if the form exists after clicking the button assert.equal(this.$('form').length, 1);. Or you could change the way the component works, but unless you want the action to bubble up you don't need to go that route.
Which might look something like
toggleForm () {
this.sendAction('toggleForm');
}
or
<button {{action toggleForm}}">Add</button>
(note the no quotes on `toggle form this time, that means call the action that was passed in.)

Ember acceptance test not seeing updated DOM

I'm still getting into the depths of Ember's acceptance testing. One issue I seem to keep having is the DOM not getting updated after an event. For example, my page has a side menu. A simple toggle changes a property in it's component which then toggles a "hide" class on the menu itself:
Component
import Ember from 'ember';
export default Ember.Component.extend({
menuHidden: true,
actions: {
toggleMenu(){
this.set('menuHidden', !this.get('menuHidden'));
},
}
});
Template
<a id="menu-toggle" class="{{unless menuHidden 'open'}}" {{ action 'toggleMenu' }}>
<span></span><span></span><span></span>
</a>
<div id="menu" class="{{if menuHidden 'hide'}}">
{{#link-to 'dashboard' invokeAction='closeMenu'}}Dashboard{{/link-to}}
{{#each menu as |child|}}
{{menu-child child=child createCase=(action 'createCase') menuHidden=menuHidden}}
{{/each}}
<a href="javascript:void(0)" {{ action 'logout' }}>Logout</a>
</div>
Acceptance Test
import { test } from 'ember-qunit';
import moduleForAcceptance from '../helpers/module-for-acceptance';
moduleForAcceptance('Acceptance | side menu');
test('side menu opens and closes', assert=>{
logIn('my#email.com', 'password');
andThen(()=>{
assert.equal(find('#menu').attr('class'), 'hide', 'Hidden by default');
click('#menu-toggle');
andThen(()=>{
assert.equal(find('#menu').attr('class'), '', 'Now visible');
});
});
});
Now this is running fine in the browser. The test is logging in fine with my custom helper (the menu is only visible when logged in) and if I drop a console.log into toggleMenu() it is being triggered by the test. But it fails the last assert. I've done a console.log on the menu's wrapper's HTML before the last assert, it's still seeing the #menu element with class=hide
Is there something obvious I'm doing wrong? I can't find many examples of people with multiple andThen calls in acceptance tests, so I've tried having it nested - as above - and pulling the second andThen out inline with the first one. No difference.
If you open the developer console in the test browser window, you will likely see this error:
Assertion failed: You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run
The following line in your component is considered code with asynchronous side-effects:
this.set('menuHidden', !this.get('menuHidden'));
Instead, for the test to work, you need to manually add the line to the run-loop, which is achieved by adding that line of code in an Ember.run, like so:
import Ember from 'ember';
export default Ember.Component.extend({
menuHidden: true,
actions: {
toggleMenu(){
Ember.run(this, function(){
this.set('menuHidden', !this.get('menuHidden'));
});
},
}
});
This will not affect the actual running code as the operations in your Ember.run will get merged into the main run-loop.
I had a similar issue which I managed to resolve after going through the steps mentioned in here

Acceptance test Redirecting to specific URL

I created an Acceptance test on ember-cli, in order to check when a user is on a URL and then clicks on the "Cancel" button on that page, the page should be redirected to other URL.
When running the test I'm getting this error:
"Promise rejected during Redirects to previous URL if cancel button is
clicked: Element .close-button not found".
But I have that element on my template. Could someone suggest me what I'm missing here or suggest me a documentation I can read in order to solved this error?.
This is my acceptance test:
import { test } from 'qunit';
import moduleForAcceptance from '/tests/helpers/module-for-acceptance';
moduleForAcceptance('Acceptance | test/company');
test('Redirects to previous URL if cancel button is clicked', function() {
return visit('test/company/add').then(function(){
return click('.close-button');
}).then(function(assert){
assert.equal(currentURL(), 'test/company');
});
});
I tried this test too, but I keep getting similar error:
test('Redirects to previous URL if cancel button is clicked', function(assert) {
visit('/test/company/add');
click ('.close-button');
andThen(function() {
assert.equal(currentURL(), '/test/company');
});
});
This is the error:
Error: Element .close-button not found.
This is part of the template where I have the cancel button( add.hbs)
<div class="form-add">
<a href="#" class="close-button" {{action 'cancel'}}>Close</a>
</div>
This is the controller, where the action 'cancel' is defined:
actions: {
cancel() {
if (this.get('isSaved')) {
return;
}
this.transitionToRoute('test/company');
}
}
Visit and click are the test helpers which will return a promise.Usually you will execute the further testing once your promises are resolved.
Try doing this:
test('Redirects to previous URL if cancel button is clicked', function(assert) {
visit('/test/company/add');
andThen(()=>{
click ('.close-button');
andThen(()=>{
assert.equal(currentURL(), '/test/company');
})
});
});

Emberjs outlet inside a bootstrap popover

I'm using bootstrap popover in my app and I need to render an outlet inside it.
I have this nested route :
this.resource('pages', function(){
this.resource('page', { path: ':id' }, function(){
this.resource('edit', function(){
this.resource('images', function(){
this.resource('image', { path: ':image_id'}, function(){
this.route('edit');
})
});
});
});
});
When the user is here => /pages/1/edit/ when he click on an image it route to /images but render the {{outlet}} inside the popover like this :
<div class="popover-content hide">
{{outlet}}
</div>
This is my popover initialisation :
$img.popover({
html: true,
content: function() {
return $('.popover-content').html(); //need to have the outlet here
}
});
So far, it render correctly my outlet, but inside the images template, I have some button that modify the DOM and it doesn't update the html. Unless if I close and open the popover again I can see the modification.
Is it possible to render the outlet directly inside the code ? or is it possible to have my popover being updated ?
Thanks for the help.
See these links for an alternative approach to putting Ember stuff in Bootstrap popovers:
Bootstrap Popovers with ember.js template
https://cowbell-labs.com/2013-10-20-using-twitter-bootstrap-js-widgets-with-ember.html
Ember and Handlebars don't like this because it's basically copying the html content of a div and plopping it into another. But that html alone isn't everything that's needed. Ember is magic and there's lots of stuff happening in the background.
Your hidden div is real ember stuff, so let's try not to mess with it by calling .html() on it. My idea is to literally move the DOM itself instead.
first, modify your popover function call to always create this placeholder div:
content: '<div id="placeholder"></div>',
next, detach the content div from the dom in the didInsertElement of the view:
// get the popover content div and remove it from the dom, to be added back later
var content = Ember.$('.popover-content').detach();
// find the element that opens your popover...
var btn = Ember.$('#btn-popup-trigger').get(0);
// ... and whenever the popover is opened by this element being clicked, find the placeholder div and insert the content element
// (this could be improved. we really just want to know when the popover is opened, not when the button is clicked.)
btn.addEventListener("click", function() {
content.appendTo("#placeholder");
});
since the content div is immediately detached when didInsertElement is called, you can remove the "hide" css class from the content div.
edit: i tried this on my own project and it broke two-way binding. the controller updated my handlebars elements, but any two-way bound {{input}} helpers did not update the controller/model. i ended up using a single-item dropdown menu, and used this to prevent the menu from closing too quickly:
Twitter Bootstrap - Avoid dropdown menu close on click inside

ember.js v1.0.0-rc.1 -- Using a modal outlet, how do I route out of the modal on close?

I'm attempting to render all my modals through application routing, but I'm having trouble figuring out the proper way to return to a previous state after I dismiss the modal.
Here's the basic setup:
I have an outlet in my application template which I'm using to display modal dialogs.
It looks something like:
{{ outlet modal }}
In my route mappings, I've defined hooks for the individual modal. For instance, my help dialog pops up with:
App.HelpRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
this.render({ outlet: 'modal' });
}
});
Right now, I can display my modal through the uri:
foo.com/#/help
I have a hook to dismiss the modal using jQuery:
$('#modalHelpWindow').modal('hide');
But this doesn't really help, because I'm just hiding the element. I need to update URI on dismissal. If I hit the same route again, the modal is already hidden, and doesn't display.
What methods should I be using to dismiss the modal, and route the application back to its previous state? Should I just be using history.back()?
Note, I am aware of this SO question, but the solution is not the preferred 'ember' way of doing things, as programmatically created views will not have their controllers associated properly What's the right way to enter and exit modal states with Ember router v2?
Looks like I can hook up the hidden handler to do a history.back() call in my view's didInsertElement method, a la:
didInsertElement: function() {
var $me = this.$();
$me.modal({backdrop:"static"});
$me.on('hidden', function() {
history.back();
});
},