Integration test for component containing a component - ember.js

I have a component that uses another component. But it doesn't work because the test for the first component says it's missing a helper called curit-select.
In 0.10 you could add a needs to themoduleForComponent` function but doing that now will force it to unit-testing mode. (see)
You do not require dependencies through needs:. Doing so will force the test into unit mode.
component 1:
<div class='change-device-status'>
Status: {{curit-select content=statuses selection=device.status}}
<button class="save-button" {{action 'save'}}>opslaan</button>
</div>
component 2:
<select {{action 'change' on='change'}}>
{{#if prompt}}
<option disabled selected={{is-not selection}}>
{{prompt}}
</option>
{{/if}}
{{#each content key="#identity" as |item|}}
<option value="{{read-path item optionValuePath}}"
selected={{is-equal item selection}}>
{{read-path item optionLabelPath}}
</option>
{{/each}}
</select>
Now I'm trying to write an integration test for the first component:
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('change-device-status', 'Integration | Component | change device status', {
integration: true
});
test('it renders', function(assert) {
assert.expect(3);
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
this.set('dev', {
status: 'InUse'
});
this.render(hbs`{{change-device-status content=dev}}`);
assert.equals(this.$().text().trim(), '');
});

It'll just work! Remember restart ember serve.

Related

Using Jest to mock a component which has other components as properties

I'm trying to mock react-bootstrap <Modal> component with jest. <Modal> contains some "sub-components" as properties, for example <Modal.Header>. I'm trying to find out the correct way to mock this kind of components using Jest.
Here's a simple component using <Modal>:
// mymodal.js
import React from 'react'
import {Modal, Button} from 'react-bootstrap'
const MyModal = ({ visible, hide, title, onOk }) =>
<Modal show={visible} onHide={hide}>
<div className='simple-modal'>
<Modal.Header closeButton>{title}</Modal.Header>
<Modal.Body>
<div>I'm body</div>
</Modal.Body>
<Modal.Footer>
<Button className='invert-primary' onClick={hide}>
Cancel
</Button>
<Button bsStyle='primary' onClick={onOk}>
Ok
</Button>
</Modal.Footer>
</div>
</Modal>
export default MyModal
And here's basic snapshot test for it:
// mymodal.test.js
import renderer from 'react-test-renderer'
import * as React from 'react'
import MyModal from './mymodal'
jest.mock('react-bootstrap', () => {
function Modal(props) {
return <div>{props.children}</div>
}
Modal.Header = 'Modal.Header'
Modal.Body = 'Modal.Body'
Modal.Footer = 'Modal.Footer'
return({
Modal: Modal,
Button: 'Button',
})
})
describe('MyModal component', () => {
test('should render a modal', () => {
const modal = renderer.create(<MyModal
visible={true}
hide={() => ''}
onOk={() => ''}
title='Title' />)
expect(modal.toJSON()).toMatchSnapshot()
})
})
And here's snapshot:
// Jest Snapshot v1
exports[`MyModal component should render a modal 1`] = `
<div>
<div
className="simple-modal"
>
<Modal.Header
closeButton={true}
>
Title
</Modal.Header>
<Modal.Body>
<div>
I'm body
</div>
</Modal.Body>
<Modal.Footer>
<Button
className="invert-primary"
onClick={[Function]}
>
Cancel
</Button>
<Button
bsStyle="primary"
onClick={[Function]}
>
Ok
</Button>
</Modal.Footer>
</div>
</div>
`;
I'm quite happy with the snapshot result, but I'd like to get better output for the <Modal> component itself so that the snapshot would contain also component's name (currenlty <div>) and props (currently no props shown).
How should the mocking be done to achieve this?
I couldn't find way to achieve this with jest mocking. Finally I went with enzyme shallow rendering, which handles the basic mocking out of box. To do spanshot matching, I serialized the enzyme wrappers using enzyme-to-json npm package.

Emberjs "eq" in component

Got a component that works just fine as follows (selectedId is definitely set):
export default Ember.Component.extend({
store: Ember.inject.service(),
items: [],
selectedId: 0,
result: '',
init () {
this._super(...arguments);
var store = this.get('store');
let items = store.findAll('dealtype');
this.set('items', items);
},
didInsertElement: function() {
// this.$().select2();
}
});
This render my component fine, but the part it never goes to true for the if statement (installed ember-truth-helpers for that)
<select style="width:100%">
<option value=""></option>
{{#each items as |item|}}
{{#if (eq selectedId item.id)}}
<option selected="selected" value="{{item.id}}">{{item.name}} YEAH</option>
{{else}}
<option value="{{item.id}}"> {{item.name}} </option>
{{/if}}
{{/each}}
</select>
Don't want to mix problems, but as you see i commented out the select2 init call. When doing that it make my select a select2 list, but the items are gone (thought still in the markup)
I would definitely recommend that you use Ember Power select component since they have solved everything you need for Select component
http://www.ember-power-select.com/
They have two component for single select entry and multiple. There is a good documentation and great support for ember cli projects
https://github.com/cibernox/ember-power-select
As for your problem I would try this - I have not tested itbut form top of my head this should solve your issue:
import Ember from 'ember';
export default Ember.Component.extend({
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent : function(){
var component = this;
Ember.run.later((function() {
Ember.$('select').select2({
minimumInputLength: 2,
delay: 250}).on('select2-open', function() {}
})
});
So what you need to use is aaferRenderEvent and run later to init your select 2 component. Please check aboe the code if all {} are properly closed. But this will get your select2 working insdie ember project.
Hope it helps.

how to execute action methods of a component from a controller or router in ember

I need assistance in executing action methods defined in ember components from outside. (even though ember follows DATA Down and Actions Up approach). My usecase is as follows
Application Template
<script type="text/x-handlebars-template" data-template-name="application">
<h2> Dialog Component </h2>
{{outlet}}
</script>
Index Template
<script type="text/x-handlebars-template" data-template-name="index">
<button {{action "showDialog1"}}>Open Dialog 1</button>
<button {{action "showDialog2"}}>Open Dialog 2</button>
{{#if openFirst}}
{{#modal-dialog name="dlg1" title="Modal 1" auto-open="true" onOpen=(action "handleDialogOpen" "dlg1") }} Content of the Dialog ...{{/modal-dialog}}
{{/if}}
{{#modal-dialog name="dlg2" title="Modal 2" onOpen=(action "handleDialogOpen" "dlg2")}} Content of the dialog ... {{/modal-dialog}}
</script>
Modal Dialog Template
<script type="text/x-handlebars-template" data-template-name="components/modal-dialog">
<div class="titlebar-title">
<span> {{title}} </span>
<a class="closeBtn" {{action "close"}}>X</a>
</div>
<div class="content">
{{yield}}
</div>
</script>
Index Controller
App.IndexController = Ember.Controller.extend({
openFirst : false,
actions : {
showDialog1 : function(){
this.toggleProperty("openFirst"); // open and close the first dialog when clicking the button.
},
showDialog2 : function(){
// want to trigger "open" action of modal-dialog component without using any conditionals(if-else) and without observing "auto-open" attribute
......
},
handleDialogOpen : function(dialogName){
if(dialogName === "dlg1"){
// do something.
}else if(dialogName === "dlg2"){
// do something
}
}
}
});
Modal Dialog Component
App.ModalDialogComponent = Ember.Component.extend({
tagName : 'div',
classNames : ['ui-dialog'],
attributeNames : ['title','name','auto-open'],
didInsertElement : function(){
if(this.get("auto-open")){
this.send("open");
}
},
actions : {
open : function(){
$(this.element).show();
this.onOpen()
},
close : function(){
$(this.element).hide();
}
}
});
Css Style Definition
ui-dialog{
display : none;
}
Is there any way to achieve this ? Kindly guide me.
Executing actions from controller inside component it not recommended approach. Why does your modal send Save and Cancel actions to the controller and router instead.
Ember has sendAction which you use to send action from component to controller or router.
Inside component you can define an action like this Ember 1.x
{{my-component action="doSomething"}}
which you can send to controller in router
MyComponent = Ember.Component.extend({
click: function() {
this.sendAction();
}
});
This will trigger doSomething from component to controller. In ember 2.x you can check them here, they are pretty much the same
https://guides.emberjs.com/v2.6.0/components/handling-events#toc_sending-actions
Lets say that you want to open modal. Inside component you would define a property called isOpen that will show hide modal content
{{my-modal isOpen=isOpen}}
There is a prop on controller that also has this field which you pass from controller to component. Inside your template you could say:
{{#if isOpen}}
{{my-modal ....this will trigger didInertElement on component that you can use to handle backdrop ...etc
{{/if}}
App Labs has a great example on how to do the modals which integrates with ember-cli
https://github.com/yapplabs/ember-modal-dialog

Testing an Ember.js component using hasBlock

I have this component set up (stripped down to its minimum):
<a href={{href}}>{{text}}</a>
{{#if template}}
<button {{action "toggleSubmenu"}} class="global-nav-toggle-submenu">
<i class="caret-down"></i>
</button>
{{/if}}
And this test:
test('has a toggle button if a submenu is present', function(assert) {
var component = this.subject({
template: Ember.HTMLBars.compile('<ul class="global-nav-submenu"></ul>')
});
assert.ok(this.$().find('.global-nav-toggle-submenu').length);
});
This runs fine, but I get a deprecation notice from Ember:
Accessing 'template' in <app-name#component:global-nav-item::ember540> is deprecated. To determine if a block was specified to <app-name#component:global-nav-item::ember540> please use '{{#if hasBlock}}' in the components layout.
When I change the template to use hasBlock instead:
<a href={{href}}>{{text}}</a>
{{#if hasBlock}}
<button {{action "toggleSubmenu"}} class="global-nav-toggle-submenu">
<i class="caret-down"></i>
</button>
{{/if}}
The test fails. Logging this.$() to the console shows that the hbs template file seems to be ignoring the template I'm adding programmatically.
In the test, I've tried swapping out template with block, layout, hasBlock, content, etc., with no success. I've tried initializing the subject with hasBlock: true as well.
And the logic runs fine when loading a page in the regular development app that has a block applied:
{{#global-nav-item text="Hello" href="#"}}
Some Content
{{/global-nav-item}}
Is there some other way that I should be setting up my components in unit tests in order to test this logic?
In general you should use the "new" style component integration tests for this type of thing.
See the following resources:
Example from the ember-radio-button addon
Nice blog post - Ember component integration tests
Update: Based on the blog post linked from Robert's answer, here is the new test code in tests/integration/components/global-nav-item-test.js:
import hbs from 'htmlbars-inline-precompile';
import { moduleForComponent, test } from 'ember-qunit';
moduleForComponent('global-nav-item', 'Integration | Component | global-nav-item', {
integration: true
});
test('has a toggle button if a submenu is present', function(assert) {
this.render(hbs`
{{#global-nav-item text="Hello" href="/"}}
<ul class="le-global-nav-submenu"></ul>
{{/global-nav-item}}
`);
assert.ok(this.$('.global-nav-toggle-submenu').length);
});

How to get the clicked element on Ember Component

I'm learning EmberJS, I tried to search the docs and stuff but I couldn't get it right so far I've implemented the component and a action to respond on click event, for now it just print something in the console. I want to get the clicked element so I could change it's style and attributes. I'm using the scaffold generated by ember-cli version 0.2.7 Follows the code:
app/components/heart-like.js
import Ember from 'ember';
export default Ember.Component.extend({
actions:{
click: function (event) {
console.log(event); // undefined
console.log("Hello from component");
}
}
});
app/templates/components/heart-like.hbs
<div class="row">
<span {{action "click"}} class="pull-right" style="color: #B71C1C;"><i class="fa fa-2x fa-heart"></i></span>
</div>
Ember.EventDispatcher delegates some events to the corresponding Ember.View. Since Ember.Component is a subclass of Ember.View, you can capture click events just exposing a click function:
export default Ember.Component.extend({
click: function (event) {
console.log(event.target); // displays the clicked element
console.log("Hello from component");
}
});
Keep in mind that using directly these event hooks isn't a good practice because you're dealing directly with the DOM, and not expressing your intention. Depending of your use case it's a better alternative to just create a property in the component and change it via actions. For instance, to change the color of the icon when it's clicked:
component
export default Ember.Component.extend({
color: 'red',
actions:{
changeColor: function() {
this.set('color', 'black');
}
}
});
template
<div class="row">
<span {{action "changeColor"}} class="pull-right" style="color: {{color}};"><i class="fa fa-2x fa-heart"></i></span>
</div>
Live demo