Why do Ember view customEvents only pass one parameter? - ember.js

I am using the twitter typeahead lib, and it can send custom events. One such event is "typeahead:selected" which is documented to pass 3 parameters to the event handler.
To catch that event in Ember, I first do this:
App = Ember.Application.create({
customEvents: {
"typeahead:selected": "onTypeaheadSelected"
}
})
and in a view, I do this:
TypeaheadTextField: Ember.TextField.extend({
onTypeaheadSelected: function(a,b,c) {
console.log("onTypeaheadSelected", arguments, a, b, c);
},
})
But only the first parameter has been set by ember, which is the jQuery event object. Params "b" and "c" are undefined.
If I instead try and listen directly on the events via jQuery using this code in my view:
didInsertElement: function () {
this.$().typeahead().on('typeahead:selected', function(a,b,c) {
console.log("onTypeaheadSelected", arguments, a, b, c);
});
}
I am getting all of "a", "b" and "c" with values, so typeahead is actually sending them.
This is ember 1.5.0.

The general implementation of event callbacks is to pass back SomeCustomEvent arg which contains all of the arguments for the event. They programmed the customEvent handler around this concept (probably short-sighted, unless there is some document out there stating it's the law of the land). https://developer.mozilla.org/en-US/docs/Web/Events
That being said, you'll need to implement it at the view layer like you did. You can create a mixin, or base class (which is essentially the same thing) so you don't need to recreate the code over and over.
App.TypeAheadView = Em.View.extend({
setupTypeAhead: function () {
this.$().typeahead().on('typeahead:selected', this.typeAhead);
}.on('didInsertElement'),
killTypeAhead: function(){
this.$().typeahead().off('typeahead:selected', this.typeAhead);
}.on('willDestroyElement'),
typeAhead:function(a,b,c) {
console.log("onTypeaheadSelected", arguments, a, b, c);
}
});
App.BlahView = App.TypeAheadView.extend();
App.CowView = App.TypeAheadView.extend();

Related

Subscribe to a DS.Model event in controller

I have the following controller :
export default Controller.extend({
/* model is an array of Posts */
didDeletePost(post) {
/* PROBLEM HERE : post is an InternalModel */
this.get('model').removeObject(post);
/* do other stuff with post */
},
actions: {
markPostForDelete(post) {
post.markForDelete(); /* starts a timer */
post.one('didDelete', this, this.didDeletePost);
},
clearMarkPostForDelete(post) {
post.clearMarkForDelete(); /* cancel a timer which will destroyRecord */
post.off('didDelete', this, this.didDeletePost);
}
}
});
But remove the post from the model in didDeletePost does not work because the post argument is the InternalModel, not the DS.Model.
How can I achieve this ?
As it does not seem to be easy to do it like this, I guess there should be a better way to implement this kind of timer ?
Actually you don't need to remove the post from model.
Please check out the twiddle I provided : https://ember-twiddle.com/25de9c8d217eafe03aea874f8eefb0fd
In my experience and from what others have told me, listening for events rather than calling actions/functions can leave you with very confusing chains of events (and this seems to exemplify that, at least to me).
I'd do it slightly more manually with a flag (here's an example all in the model for simplicity, tho you might need to move to the controller depending on other interactions):
export default DS.Model.extend({
markPostForDelete() {
this.set('marked', true).then( () => /*function in model to start timer and then call this.deleteMarked() */);
},
clearMarkPostForDelete() {
this.set('marked', false)
},
deleteMarked() {
if(this.get('marked')) {
this.destroyRecord();
}
}
}); /* end of model */

React test; how to mock componentDidMount or overwrite it?

I'm trying to test a react component.
var Component = React.createClass({
componentDidMount: function () {
return this.setState({
name: 'blabla'
});
},
render: function () {
return (
<h1>{this.state.name}</h1>
);
}
});
Is there a way, during testing, to mock what componentDidMount returns or does? That would leave me to test it on it's own and just test the component render behaviour.
Thanks!
I prefer the following approach, but requires using ES6 classes.
// component.jsx
class Component extends React.Component {
componentDidMount() { return this.setState({name: 'blabla'}); }
render() { return (<h1>{this.state.name}</h1>); }
}
//component-spec.jsx
describe('Component', () => {
it('does stuff', () => {
let ComponentTest = class extends Component {
componentDidMount() {
// your override here
}
};
let component = TestUtils.renderIntoDocument(<ComponentTest />);
//expect(component...).toEqual(...)
});
});
The point is to create an on demand ChildClass inheriting the OriginalClass,
do whatever overrides and then TestUtils.renderIntoDocument(<ChildClass />)
The idea here, if I understand correctly, is that you're trying to stub out a function before a component is rendered in your test. In your case, componentWillMount is only called once in a component's lifecycle, immediately before the component is rendered. So you can't just render the component and then stub out the function, it must be done before the render occurs.
Let's take these components for example:
parent.js
var Child = require('./child.js');
var Parent = React.createClass({
render : function () {
return (
<div className="parent">
<Child/>
</div>
);
}
});
module.exports = Parent;
child.js
var Child = React.createClass({
test : function () {
return true;
},
render : function () {
if (this.test) {
throw('boom');
}
return (
<div className="child">
Child
</div>
);
}
});
module.exports = Child;
Here, we would want to stub out the test function before our Child component is rendered, otherwise, it will blow up.
I have been able to do this using jasmine-react. These helper functions provide some useful functionality when running tests, almost to the point where TestUtils can be ditched completely.
jasmineReact.render(component, [container]) will render an instance of component into the DOM node specified in [container]. This is like TestUtils.renderIntoDocument(), except it renders the component into an attached DOM node instead of a detached DOM node. It will also perform the necessary cleaning operations when the test is finished.
jasmineReact.spyOnClass(componentClass, functionName) will stub out a particular function belonging to a component class. This behavior is maintained until the end of the test, which means that you can call this function before a component is rendered. This, if I understand correctly, is what you're looking for.
So, using these two helper functions, I can write a test for the code shown above that looks something like this:
var React = require('react/addons'),
Parent = require('./parent.js'),
Child = require('./child.js'),
jasmineReact = require('jasmine-react-helpers');
describe('Parent', function () {
it('does not blow up when rendering', function () {
jasmineReact.spyOnClass(Child, 'test').and.returnValue(false);
var parentInstance = jasmineReact.render(<Parent/>, document.body); //does not blow up
expect(parentInstance).toBeTruthy(); //passes
});
});
Let me know if you have any questions.
I've found two ways to go about this (i'm sure there are more).
1) I've used sinon-chai and required in the base element class and then use rewireify to put a set a spy on the componentWillMount method. This works but not sure what test suites you're using.
2) Probably the easier way. Is to just use the TestUtils to get an instance of the component and then just manually run the componentWillMount method.
That second way would probably look something like (forgive the pesudo code):
it('should call state when it first mounts', function () {
var Component = require('../my-component');
var component = TestUtils.renderIntoDocument(<Component />);
component.setState({name: null});
component.componentWillMount();
expect(component.state.name).to.equal('blabla');
});

ngAnimate unit test not adding class

I'm new to unit testing as well as the ng-animate module. I made a simple directive to test out ng-animate.
.directive('slideShow', function ($animate, $compile) {
return {
template: '<div class="slide-show-container"></div>',
restrict: 'EA',
replace: true,
link: function (scope, element, attrs) {
var newElement = $compile('<div class="slide-show-slide"></div>')(scope);
element.bind('mouseenter',function() {
element.append(newElement);
$animate.addClass(newElement, 'slide-enter');
});
element.bind('mouseleave',function() {
$animate.removeClass(newElement, 'slide-enter');
});
}
};
});
Then I made the following unit test to confirm that the .slide-enter class was being added.
it('should add slide-enter class', function () {
element.triggerHandler( "mouseenter" );
expect(element.children().hasClass("slide-enter")).toEqual(true)
});
The directive correctly added the class when I moused over it in a manual test. However the unit test failed and showed that the slide-enter class wasn't being added.
Finally I figured out the only way I could fix it was wrapping the unit test in a $timeout:
it('should add slide-enter class', inject(function ($timeout) {
element.triggerHandler( "mouseenter" );
$timeout(function() {
expect(element.children().hasClass("slide-enter")).toEqual(true);
});
$timeout.flush();
}));
Can anyone help me understand why this $timeout is required for the test to work? Is there another way to get this unit test to work that I'm messing?
NOTE I am using angular-animate 1.2.0-rc.2 and have documented my findings with this version. The need for the $timeout.flush() call seems to be fixed when looking at the 1.2.0-rc.3 code but I have not tested it yet. https://github.com/angular/angular.js/blob/v1.2.0-rc.3/src/ngAnimate/animate.js
I had the same problem with one of my tests. I was able to get my test to work by just calling $timeout.flush() after I had called the code that was supposed to trigger the adding of the class and before I called the expect. Your test should work if you rewrite it like:
it('should add slide-enter class', inject(function ($timeout) {
element.triggerHandler( "mouseenter" );
$timeout.flush();
expect(element.children().hasClass("slide-enter")).toEqual(true);
}));
I had to dig into the ngAnimate code to figure it out and this is what I found.
If you take a look at the angular-animate.js file at the addClass function. You will see the following:
addClass : function(element, className, done) {
performAnimation('addClass', className, element, null, null, function() {
$delegate.addClass(element, className, done);
});
}
The closure that is the last parameter to performAnimation is what will finally add the class.
In performAnimation, that last parameter is named 'onComplete`. There is a section of code that deals with calling this closure when animations should be skipped:
//skip the animation if animations are disabled, a parent is already being animated
//or the element is not currently attached to the document body.
if ((parent.inheritedData(NG_ANIMATE_STATE) || disabledAnimation).running) {
//avoid calling done() since there is no need to remove any
//data or className values since this happens earlier than that
//and also use a timeout so that it won't be asynchronous
$timeout(onComplete || noop, 0, false);
return;
}
And there is the call to $timeout that is causing the problem. When running this code in an angular test, the call to $timeout simply queues up the closure. The test code then has to call $timeout.flush() in order to get that function to run.

How to know whether a method is a call made by me thgh code or its from observer

I have a method in a view just like following.
testMethod : function() {
//run code
}.observes('property1')
This method can either be trigerred directly by calling or triggered by the property1 observer. Is it possible to know inside the method, which way the call is getting triggered. Thanks
When observer is called, it receives 2 arguments: the controller object, and the observed property which has changed and triggered the observer.
So you can check it like this:
testMethod : function() {
if(arguments.length === 2 && arguments[1] === 'property1'){
// you're triggered by property observer
} else {
// triggered directly
}
}.observes('property1')
This, of course, can be spoofed by caller..
I have stumbled upon this myself and have found no way to do so. I ended up doing something like this:
testMethod : function() {
//run code
},
propertyObserver : function(){
this.testMethod();
}.observes('property1')

How to verify event subscribed using Moq

I am using MVVM pattern and silverlight 4.0 and Moq for testing.
In the view model constructor, am passing an IEventAggregator object. This object is used to subscribe to an event called SelectionChangedEvent.
In the test method I am doing like this:
this.selectedEvent = new Mock<SelectionChangedEvent>();
this.eventAggregator.Setup(x => x.GetEvent<SelectionChangedEvent>()).Returns(this.selectedEvent.Object);
var viewModel = new ViewModel(this.eventAggregator);
I want to test that the event is getting subscribed when the constructor is called.
How can I verify this?
[Disclaimer: I haven't been able to test this under Silverlight]
Here's a possible solution that basically executes a callback to set an external boolean when Subscribe is called on the event. See comments below though.
[Test]
public void Constructor_CallsSubscribeOnSelectionChangeEvent()
{
var subscribeCalled = false;
var selectedEvent = new Mock<SelectionChangedEvent>();
var eventAggregator = new Mock<IEventAggregator>();
selectedEvent
.Setup(x => x.Subscribe(
It.IsAny<Action<object>>(),
It.IsAny<ThreadOption>(),
It.IsAny<bool>(),
It.IsAny<Predicate<object>>()))
.Callback<Action<object>, ThreadOption, bool, Predicate<object>>
((action, option, f, pred) => { subscribeCalled = true; });
eventAggregator
.Setup(x => x.GetEvent<SelectionChangedEvent>()).Returns(selectedEvent.Object);
var viewModel = new ViewModel(eventAggregator.Object);
Assert.That(subscribeCalled, Is.EqualTo(true));
}
The above is pretty ugly, mostly due to the fact the the only mockable (virtual) overload of the Event's Subscribe method takes four arguments and that the argument types of Callback() can't be deduced automatically - which leads to lots of "extraneous" code.
An alternative would be to instead mock EventBase.InternalSubscribe, which only takes a single argument; but as that method is protected this approach has its own caveats.