I am new to Jasmine and seem to be struggling to get what I think is a fairy standard kind of thing running.
I am loading an HTML file via a fixture and trying to call a click on an element on the dom. This I would expect result in the call to the method of the JS file I am trying to test. When I try and debug this in developer tools the method that should be called in my js file never hits a breakpoint. As such I assume that code is not being called and therfore does not toggle the expand/collapse class.
My test:
describe("userExpand", function () {
beforeEach(function () {
loadFixtures('user-expand.html');
//userControl();
//this.addMatchers({
// toHaveClass: function (className) {
// return this.actual.hasClass(className);
// }
//});
});
//this test works ok
it("checks the click is firing", function () {
spyOnEvent($('.expanded'), 'click');
$('.expanded').trigger('click');
expect("click").toHaveBeenTriggeredOn($('.expanded'));
});
//this doesn't
it("checks the click is changing the class", function () {
//spyOnEvent($('.collapsed'), 'click');
var myElement = $('.collapsed');
myElement.click();
expect(myElement).toHaveClass('.expanded');
});
Part of the fixture:
<div class="wrapper">
<div class="row group">
<div class="col-md-1" data-bordercolour=""> </div>
<div class="collapsed col-md-1"> </div>
<div class="col-md-9">None (1)</div>
The JS I am trying to test:
var userControl = function () {
"use strict";
var collapse = '.collapsed';
var expand = '.expanded';
var userList = $(".userList");
function toggleState() {
var currentControl = $(this);
if (currentControl.hasClass('all')) {
if (currentControl.hasClass('expanded')) {
toggleIcon(currentControl, collapse);
userList.find(".user-group-summary").hide()
.end()
.find(".user-group-info").show();
} else {
toggleIcon(currentControl, expand);
userList.find(".user-group-summary").show()
.end()
.find(".user-group-info").hide();
}
} else {
currentControl.parent().nextUntil('.group').toggle();
currentControl.toggleClass("expanded collapsed");
currentControl.parent().find(".user-group-summary").toggle()
.end()
.find(".user-group-info").toggle();
}
};
function toggleIcon(ctrl, currentState) {
var details = ctrl.closest('div.row').siblings('.wrapper');
details.find(currentState).toggleClass('expanded collapsed');
if (currentState === expand) {
details.find('.detail').hide();
} else {
details.find('.detail').show();
}
}
userList.on('click', '.expanded, .collapsed', toggleState);
$('[data-bordercolour]').each(function () {
$(this).css("background-color", $(this).data('bordercolour'))
.parent().nextUntil('.group')
.find('>:first-child').css("background-color", $(this).data('bordercolour'));
});
return {
toggleState: toggleState
};
}();
The code works fine in normal use so I am sure I am missing something obvious with the way Jasmine should be used. Any help would be appreciated.
Update:
I can make the togglestate method fire by using call in the test rather than triggering a click event:
it('checks on click of icon toggles that icon', function () {
var myElement = $('.collapsed');
userControl.toggleState.call(myElement);
expect(myElement).toHaveClass('expanded');
});
This seems a little strange as all the examples I can find are quite happy with click. Gets me off the hook but I would still like to know what I am missing.
It's hard to give a precise hint without the source code. Does click on .collapsed involve asynchronous action(s)? If so, wrapping the test in runs(...); waitsFor(...); runs(...); may solve the problem. Check the Jasmine introduction for how to do this.
Related
I'm sure this is something simple that I am missing but I'm at a loss.
I have this block of jQuery:
jQuery("span.frm_inline_total").digits();
jQuery(".frm_input_group").on("blur", "input", function () {
jQuery("span.frm_inline_total").digits();
});
jQuery(".frm_range_container input").mouseup(function () {
jQuery("span.frm_inline_total").digits();
console.log("mouse up");
});
jQuery(".frm_range_container input").mousedown(function () {
jQuery("span.frm_inline_total").digits();
console.log("mouse down");
});
That calls a function to place commas in some field numbers. I don't think it's relevant, but here is the function:
jQuery.fn.digits = function () {
return this.each(function () {
jQuery(this).text($(this).text().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,"));
})
}
My issue is this. Everything works except when I try to call digits() using mouseup(). It logs the mouseup() event with 'console.log', and the mousedown() event correctly works, but no mouseup(). ...alert("mouse up") works, just not 'digits'.
For what it's worth, I'm placing this event on a built-in slider in a drag-and-drop website I am editing. My "development" is limited to client side code. There is already an event on it to retrieve the new values that I thought might be interfering, but then I don't understand why it would fire logs or alerts.
Assuming your HTML structure is something like this:
<div class="frm_range_container">
<div class="frm_input_group">
<span class="frm_inline_total">Value to replace</span>
<input value="Click me"></input>
</div>
</div>
and the rest of your code works, changing the code like below should produce desired output.
// added logs to check in console, digits function is the same
$.fn.digits = function () {
console.log('digits'); // test to see if reaches digits() function
return this.each(function () {
// this should be the correct element.
$(this).text(
$(this).text().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
);
})
}
$(".frm_range_container input").on('mouseup mousedown', function (e) {
console.log(e.type);
$("span.frm_inline_total").digits();
});
If you want to only target span.frm_inline_total contained in each frm_range_container, you can use $("span.frm_inline_total", this).digits(); for that
i have a structure like this :
// App.js
var APP = {
viewIndex: function(){
EVT.doSomething();
},
// Another function below
}
// Event.js
var EVT = {
doSomething: function(){
deleteField();
function deleteField(){
$("body").on("click", "#btn", function(){
console.log("Clicked");
})
}
}
}
my project is SPA wannabe, so when i want to change the page, it must execute some function inside App.js, but my problem is, when i call APP.viewIndex() (when i go to Index, go back, and go to index again(without refreshing page) ), the function inside EVT -> doSomething() is execute twice, so i have no idea how to prevent it,
in my console i got this :
Clicked
Clicked
*sorry if my explanation is a bit complicated
hope you guys can help me out of this problem :D
thanks
Use a property to remember if you already called deleteField().
var EVT = {
didSomething: false,
doSomething: function(){
if (!this.didSomething) {
deleteField();
this.didSomething = true;
}
function deleteField(){
$("body").on("click", "#btn", function(){
console.log("Clicked");
})
}
}
}
I am having an issue when enabling deferred updates in the knockout library. I have implemented Jquery datatables as a component, when navigating to a view that has this component i can see the following methods being called in order.
Getview>Activate>Attach
everything works as expected
But if i press f5 and refresh the page rather than navigating to it from another page it breaks and the following methods are called
Getview>Activate>Attach>Getview>Activate>Attach>Detach>Detach (not sure why its called twice in the end)
and it breaks, no table shows on the UI at all as it does not render from what i can tell, i think it has something to do with durandal transitions and there being a difference between navigating to a page and refreshing a page kinda grasping at straws tho.
This is a minimal class that replicates the problem for me, note i dont have an HTML file for this component i want to use the getView method to pass in some dynamic HTML from JQueryDT
I created a quick sample project with the bare minimum needed to replicate the problem.
https://bitbucket.org/dchosking1988/deferred-update-example
If you pull that and run it you will see that the "hello world" will disappear when you refresh the page but it wont if you navigate between tabs.
the general steps i used to replicate the issue are
1) download sample project
2) add test component (see repo above for the sample file)
3) enable deferred updates
4) disable view caching
4) try compose new instance of the component
Edits to give more info
*This is not a JQuery Datatable problem, it is replicated with the following
So you dont have to download the gitRepo, this is the code i can replicate the problem with in the sample project following the above steps.
define([],
function () {
var test = function () {
var self = this;
var defaultViewHtml = '<div> <h1>Hello World</h1></div>';
var currentView = null;
self.getView = function () {
console.log('GetView');
if (!currentView) {
currentView = $(defaultViewHtml)[0];
}
return currentView;
};
self.activate = function (activateOptions) {
console.log('Activate');
};
self.attached = function (view, parent, settings) {
console.log('Attatched');
};
self.detached = function (view, parent) {
console.log('Detatched');
};
};
return test;
});
Then Add this HTML to the index.html, also dont forget to create an instance of the class in the index.js
<div class="whiteRow">
<div class="container">
<div class="row">
<div class="col-md-12">
<div data-bind="compose: { model: test }"></div>
</div>
</div>
</div>
</div>
This is occurred because it call code twice and second called the currentView stay empty in test.js, I commented the stretch where you set the currentView and code work.
self.getView = function () {
console.log('GetView');
//if (!currentView) {
// currentView = $(defaultViewHtml)[0];
//}
return currentView;
};
-
<div class="whiteRow">
<div class="container">
<div class="row">
<div class="col-md-12">
<div data-bind="compose: { model: test }"></div>
</div>
</div>
</div>
</div>
define([],
function () {
var test = function () {
var self = this;
var defaultViewHtml = '<div> <h1>Hello World</h1></div>';
var currentView = null;
self.getView = function () {
console.log('GetView');
return currentView;
};
self.activate = function (activateOptions) {
console.log('Activate');
};
self.attached = function (view, parent, settings) {
console.log('Attatched');
};
self.detached = function (view, parent) {
console.log('Detatched');
};
};
return test;
});
currentView stay empty in test.js,
Been looking to figure out how with Twitter Flight can attach to dynamic created elements.
Having the following HTML
<article>Add element</article>
And the following component definition
var Article = flight.component(function () {
this.addElement = function () {
this.$node.parent().append('<article>Add element</article>');
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
Once a new element is created, the click event doesn't fire. Here's the fiddle: http://jsfiddle.net/smxx5/
That's not how you should be using Flight imho.
Each component should be isolated from the rest of the application, therefore you should avoid this.$node.parent()
On the other hand you can interact with descendants.
My suggestion is to create an "Articles manager" component that uses event delegation.
eg.
http://jsfiddle.net/kd75v/
<div class="js-articles">
<article class="js-article-add">Add element</article>
<div/>
and
var ArticlesManager = flight.component(function () {
this.defaultAttrs({
addSelector: '.js-article-add',
articleTpl: '<article class="js-article-add">Add element</article>'
});
this.addArticle = function () {
this.$node.append(this.attr.articleTpl);
};
this.after('initialize', function () {
this.on('click', {
addSelector: this.addArticle
});
});
});
ArticlesManager.attachTo('.js-articles');
Try attaching Article to each new article added:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/smxx5/2/
var Article = flight.component(function () {
this.addElement = function () {
var newArticle = $('<article>Add element</article>');
this.$node.parent().append(newArticle);
Article.attachTo(newArticle);
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
The Article.attachTo('article'); at the end, that runs once on load, will only attach to existing article elements.
I hit this problem, and worked around is as follows...
Javascript: All thrown together for brevity, but could easily be separated.
(function(){
var TestComponent, LoaderComponent;
TestComponent = flight.component(function() {
this.doSomething = function()
{
console.log('hi there...');
};
this.after('initialize', function() {
this.on('mouseover', this.doSomething);
});
});
LoaderComponent = flight.component(function() {
this.attachComponents = function()
{
TestComponent.attachTo('.test');
};
this.after('initialize', function() {
// Initalise existing components
this.attachComponents();
// New item created, so re-attach components
this.on('newItem:testComponent', this.attachComponents);
});
});
LoaderComponent.attachTo('body');
}());
HTML: Note that one .test node exists. This will be picked up by Flight on initialization (i.e. not dynamic). We then add a second .test node using jQuery and fire off the event that the LoaderComponent is listening on.
<div class="test">
<p>Some sample text.</p>
</div>
<script>
$('body').append('<div class="test"><p>Some other text</p></div>').trigger('newItem:testComponent');
</script>
This is obviously a very contrived example, but should show that it's possible to use Flight with dynamically created elements.
Hope that helped :)
I have a block of code like so:
function doSomething() {
someVar.on("event_name", function() {
$('#elementId').click(function(e) {
doSomething();
});
});
}
// and on document ready
$(function () {
$('#anotherElemId').click(function () {
doSomething();
});
});
The problem that I'm encountering is that when I call doSomething() from anotherElemId click event(that is binded on document ready) it works as expected, but calling it recursively from elementId click doesn't work.
Any ideas? Thinking is something trivial that I'm missing.
Is someVar an actual jQuery reference to a dom element? (e.g. $('#someitem'))
The second problem is you cant put a .click event inside a function that you would like to instantiate later on. If you are trying to only allow #elementId to have a click event AFTER some previous event, try testing if a tester variable is true:
var activated = false;
$(function () {
$('#anotherElemId').click(function () {
activated = true;
});
$('#secondElemId').on("event_name", function() {
if (activated) {
// code that happens only after #anotherElemId was clicked.
}
});
});