How to test the Knockout.js click binding with Jasmine - javascript

I'm having a problem with a jasmine test together with a knockout-template:
The html is similar to this:
<body>
<my-widget params="value: $data></my-widget>
</body>
the widget has a click-binding:
<div class="my-widget">
<a id="#clickme" data-bind="click: doSomething">Click me</a>
</div>
the widget-javascript is like this:
ko.components.register('my-widget', {
viewModel : function(params) {
this.doSomething = function() {
// doing something
};
},
template: {
require: 'text!../../templates/my-widget.html'
}
});
All of this works perfectly in production, but in Jasmine/Jquery, triggering a click on $('#clickme') does not execute the doSomething.
The following is an excerpt from my jasmine test (It's been greatly simplified but should contain the essentials):
beforeEach(function (done) {
require(['specHelpers', 'knockout'],
function (specHelpers, knockout) {
specHelpers.loadFixtureIntoPage("page.html", "myPage"); // template and id to use
expect($('#myPage')).toExist();
done();
});
});
it("WILL NOT TRIGGER MY CLICK", function (done) {
ko.applyBindings(myPage.pageViewModel, $('#myPage'))[0]);
setTimeout(function() {
$('#clickme').click();
// doSomething is not called :(
done();
}, 300);
});
When console.logging the #clickme element I can see that it is present.
It seems that the click binding in the widget does not get applied properly. However, when I run the test in bdd and it's over and failed - I can manually click this element and doSomething does get called.
What am I doing wrong? As I said, running the actual application works perfectly. It just seems that jasmine cannot handle the click bindings properly - I don't have this problem with the regular click events that are set in the document.ready

You really shouldn't be testing button clicks like that - you can be more certain if you just call the function doSomething() directly. Same way that you don't test any internals of JQuery yourself.
If you really really want to test events on a fixture, have you tried just
$("#clickme").trigger('click');
Also, double check that fixture is inserted into DOM in when you debug the test (say via browser)

Related

How to work with Knockout Code from a JQuery library file

I know this is not quite possible or rather feasible. But , this is what I intend to do .
I have a JQuery Plugin named sumoselect (https://hemantnegi.github.io/jquery.sumoselect/) for multi select dropdown like feature which has a static link at the bottom.
addSumoSelect: function () {
$('#intGroup1').SumoSelect({ createNew: true, countList : true});
}
This createNew() is a function defined in sumoselect JQuery plugin file :
createNew: function () {
var O = this;
O.optDiv.append($('<ul><li><a class="ispicon ispicon_plus" href="#addInt" data-toggle="modal" data-target="#addSchedule" data-bind="click:$parent.addGroup" title="Create New"> Create New</a></li></ul>'));
}
But the problem is JQuery is not able to parse data-bind and parent syntaxes as I suppose they are native to Knockout JS.
So , the click event is not being fired.
What can I do to make it work ?
The addGroup function is defined in my Knockout JS file.
UPDATE
Should I try to do something like :
$("#intrusionGroup1").click(function (element) {
element.parentNode.addIntrusion();
});
One possible way of rendering an HTML content which is wrapped in a string is to use the html binding, but for that too, you will need to use another knockout binding (unfortunately). So, the easiest way of doing this that I can think of now is to use jQuery itself and register a click event for that list item. For that you will need to make sure to have unique selectors for the list items.
You'll have to create a custom binding that initializes the widget and injects the custom html. Because a binding's init method is executed during ko.applyBindings, it has the chance to inject child elements early enough for them to be data-bound.
A data-bind has an init method that is called once when ko.applyBindings happens, and an update method that is called whenever a linked observable changes.
It would look something like this:
ko.bindingHandlers.sumoselect = {
init: function(elem, valueAccessor) {
// Initialize the widget, something like:
var widget = $(element).sumoselect(/* options */);
// By now, the new HTML should be injected and data-bound
// after `init` returns
// I'm not sure if the widget has a dispose method, but
// it's important to know you'll have to take care of this
// yourself.
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
// Something like:
// widget.destroy()
});
}
};
An example that shows you what won't work:
ko.applyBindings({
onClick: function() { console.log("CLICK"); }
});
document.querySelector(".myDiv").innerHTML += "<button data-bind='click: onClick'>click me</div>";
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Test button:</p>
<div class="myDiv"></div>
An example that shows you what will work:
ko.bindingHandlers.injectButton = {
init: function(element) {
element.innerHTML += "<button data-bind='click: onClick'>click me</div>";
}
}
ko.applyBindings({
onClick: function() { console.log("CLICK"); }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Test button:</p>
<div class="myDiv" data-bind="injectButton"></div>

Hiding elements until load has been completed (ng-cloak isn't what I needed)

I'm writing an AngularJS application, but I'm quite new to it, so in the code below, please point out any issues that you have.
I do have my AngularJS application defines like that:
var OfficeUIModule = angular.module('OfficeUI', ['ngSanitize']);
Then, I do have a couple of services that loads data from various JSon files.
But i won't post the code here, because I do believe that irrelevant.
Then I do have my controller which basically looks like this:
OfficeUIModule.controller('OfficeUIController', function($scope) { });
Now, I want the controller to execute some logic on startup and show a DIV element on it.
Here's the case what I want to do:
Show a Loading DIV element during initial setup.
Show a Displaying DIV element when the initialization has been done.
Let's say that in my controller I do have method to initialize the application:
OfficeUIModule.controller('OfficeUIController', function($scope) {
function Init() {
// I'll load all the data from the services here.
}
});
In order to call this method on startup, I just define it in my controller like so:
OfficeUIModule.controller('OfficeUIController', function($scope) {
Init();
function Init() {
// I'll load all the data from the services here.
}
});
And the HTML which I would like to use is the following:
<body>
<div id="loading">LOADING</div>
<div id="displaying">DISPLAYING</div>
</body>
When the page is initially loaded, I would like to show the element 'Loading'.
When the page has been loaded, I would like to show the element 'Displaying'.
In order to make this testable, I've changed my controller method Init to include a timeout of 5 seconds, just like below:
OfficeUIModule.controller('OfficeUIController', function($scope) {
Init();
function Init() {
setTimeout(function() {
alert('Page has been loaded. When you click OK the displaying DIV should be showed.');
}, 5000);
}
});
So, in this particular case, I would like to make sure that the page is being loaded, displaying the LOADING div and after 5 seconds, display the DISPLAYING div.
I would also like not to see any flickering, and therefore ng-cloak can be used I've read, but I can't manage to configure it correctly.
Here's what I've tried already:
LOADING
DISPLAYING
However, that setup doesn't work quite well.
I've included a Plunker so that you can test the solution you provide.
It happens because you are using setTimeout with anonymous function and angularjs isn't aware of model change
http://plnkr.co/edit/FJ2lnrmtG7P3HXZuxRkH?p=preview
if you use angularjs $timeout it works
$timeout(function() {
$scope.initialized = true;
}, 5000);
you could also use $scope.$digest() or $scope.$apply to make it work
You can use the angular version of window load event.
angular.element($window).bind('load', function(e) {
// code to execute
});

Unit testing with Jasmine, expect().toBeVisible()

I need to detect the CSS display value after a click event has been executed. I am sure it is user error, but I cannot get Jasmine.js to report if an elements visibility has been changed after the click.
Here is the codepen link: http://codepen.io/nicholasabrams/pen/dPYoJr
Here is the code itself: (this is obviously not the actual case that it is being used in however it is a very simplified version that recreates the issue I am having currently)
$('#button1').on('click', function(){
$(this).append('<button id="#button2">!!!!!</button>');
});
Here is the test itself:
describe("button#1 click test", function(){
beforeEach(function(){
$('#button1').click();
});
it("Should inject a new button with #button2 as the id", function(){
expect($('#button2')).toBeVisible();
});
});
At first - in your pen you should add external script for jasmine-jquery. Next, button in a button, it's illegal according to W3C Phrasing content, but there must be no interactive content descendant. Jasmine is just fine, try to append to parent:
$('#button1').on('click', function(){
$(this).parent().append('<button id="#button2">!!!!!</button>');
});
Working pen http://codepen.io/anon/pen/zxvGja?editors=101

Combination of $(document).ready and $scope.$on('$viewContentLoaded', function() {...})

I'm using JQuery UI components in a view of AngularJS/JQuery application.
I need something like this (does not work) in my JavaScript code:
$(document).ready(function () {
var elem = $('div[ng-view]')[0];
var $scope = angular.element(elem).scope();
$scope.$on('$viewContentLoaded', function() {
// multiple JQuery statements
// to support JQuery-UI componenets
});
});
This code is included as <script> into index.html that has <div class="container" ng-view> element.
My thinking was that we need a two-step process:
First JQuery reacts on document-ready HTML event and attaches a listener to Angular's $viewContenLoaded using $scope retrieved using [ng-view] element.
Then each time a view is loaded my JQuery code will be executed and JQuery UI components get activated and wired.
Apparently my logic is flawed somewhere. Please point me in the right direction.
ADDITIONAL INFO (posted 03/31/14):
The rest of my code (controllers, service, routing) is written in TypeScript.
That element needs to be compiled in order to bind angulars scope to that element. You could try something like:
var scope = angular.injector(['ng']).get('$rootScope').$new();
var compile = angular.injector(['ng']).get('$compile');
compile(elem)(scope);
scope.$on('$viewContentLoaded', function(){
// Your code
});
Though I would suggest putting your code in a directive. The code I shown above is nothing more than a hack and dangerous since now you have global access to your services.

Intercept hyperlink clicks

I'm trying to intercept clicks on this link:
<a id="test-button" href="#">Test</a>
with this js:
(function() {
$('#test-button').click(function (event) {
console.log("This code never gets called.")
event.preventDefault();
$('#alert-placeholder').html(['<div class="alert"><a class="close"',
'data-dismiss="alert">×</a>',
'<span>"+message+"</span></div>'].join())
return false;
})
console.log("yes, this code loads");
debugger;
})();
but the URL '#' loads and the code in the click() function doesn't run. What am I missing?
I'm using this code in a flask app using bootstrap.
Seems like you're trying to attach an event handler to the element that doesn't exist yet
(function() {
})();
only creates a local scope but doesn't guarantee DOM to load. To confirm it - add console.log($('#test-button').length); to your code.
What you need is to wrap your code with
$(function() {
// your code is here
});
instead

Categories