How to test a function which has a setTimeout with jasmine? - javascript

I need to write a test for a function that has a setTimeout() call inside, but i can't find how i should do.
This is the function
// Disables all submit buttons after a submit button is pressed.
var block_all_submit_and_ajax = function( el ) {
// Clone the clicked button, we need to know what button has been clicked so that we can react accordingly
var $clone = $( el ).clone();
// Change the type to hidden
$clone.attr( 'type', 'hidden' );
// Put the hidden button in the DOM
$( el ).after( $clone );
// Disable all submit button. I use setTimeout otherwise this doesn't work in chrome.
setTimeout(function() {
$( '#facebook input[type=submit]' ).prop( 'disabled', true );
}, 10);
// unbind all click handler from ajax
$( '#facebook a.btn' ).unbind( "click" );
// Disable all AJAX buttons.
$( '#facebook a.btn' ).click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
} );
};
And this is my test
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
console.log( 'f' );
expect( el ).toHaveProp( 'disabled', true );
} );
} );
I've tried using jasmine.Clock.useMock(); and jasmine.Clock.tick(11); but i couldn't get things to work, the test never pass

The overall approach varies based on your Jasmine version.
Jasmine 1.3
You can use waitsFor:
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
// Wait 100ms for all elements to be disabled.
waitsFor('button to be disabled', function(){
var found = true;
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
if (!el.prop('disabled')) found = false;
});
return found;
}, 100);
});
You could also use waits if you know exactly how long it will take:
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
// Wait 20ms before running 'runs' section.
waits(20);
runs(function(){
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
});
});
There is also a third way of doing this, without the need for waits, waitsFor, and runs.
it( "Disable all submit buttons", function() {
jasmine.Clock.useMock();
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
jasmine.Clock.tick(10);
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
});
Jasmine 2.0
You can use done, the test callback:
it( "Disable all submit buttons", function(done) {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
utility_functions.block_all_submit_and_ajax( $button.get(0) );
setTimeout(function(){
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
// Let Jasmine know the test is done.
done();
}, 20);
});
you can mock out the timer behavior:
it( "Disable all submit buttons", function() {
jasmine.clock().install();
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
jasmine.clock().tick(10);
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
expect( el ).toHaveProp( 'disabled', true );
});
jasmine.clock().uninstall()
});

For anyone googling this, a better answer can be found timer testing
import { fakeAsync, tick, discardPeriodicTasks } from '#angular/core/testing';
it('polls statusStore.refreshStatus on an interval', fakeAsync(() => {
spyOn(mockStatusStore, 'refreshStatus').and.callThrough();
component.ngOnInit();
expect(mockStatusStore.refreshStatus).not.toHaveBeenCalled();
tick(3001);
expect(mockStatusStore.refreshStatus).toHaveBeenCalled();
tick(3001);
expect(mockStatusStore.refreshStatus).toHaveBeenCalledTimes(2);
discardPeriodicTasks();
}));

Since Jasmine 2 the syntax has changed: http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support
You now can simply pass a done callback to beforeEach, it, and afterEach:
it('tests something async', function(done) {
setTimeout(function() {
expect(somethingSlow).toBe(true);
done();
}, 400);
});
Update: Since writing this it's now also possible to use async/await which would be my preferred approach.

I've never done any testing with jasmine, but I think I understand your problem. I would restructure the code a little to allow for you to wrap the function being called in a proxy function like this:
Modify your code that is being test to extract the setTimeout code into another function:
Original Code:
// Disables all submit buttons after a submit button is pressed.
var block_all_submit_and_ajax = function( el ) {
// Clone the clicked button, we need to know what button has been clicked so that we can react accordingly
var $clone = $( el ).clone();
// Change the type to hidden
$clone.attr( 'type', 'hidden' );
// Put the hidden button in the DOM
$( el ).after( $clone );
// Disable all submit button. I use setTimeout otherwise this doesn't work in chrome.
setTimeout(function() {
$( '#facebook input[type=submit]' ).prop( 'disabled', true );
}, 10);
// unbind all click handler from ajax
$( '#facebook a.btn' ).unbind( "click" );
// Disable all AJAX buttons.
$( '#facebook a.btn' ).click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
} );
};
Modified Code:
// Disables all submit buttons after a submit button is pressed.
var block_all_submit_and_ajax = function( el ) {
// Clone the clicked button, we need to know what button has been clicked so that we can react accordingly
var $clone = $( el ).clone();
// Change the type to hidden
$clone.attr( 'type', 'hidden' );
// Put the hidden button in the DOM
$( el ).after( $clone );
// Disable all submit button. I use setTimeout otherwise this doesn't work in chrome.
setTimeout(disableSubmitButtons, 10);
// unbind all click handler from ajax
$( '#facebook a.btn' ).unbind( "click" );
// Disable all AJAX buttons.
$( '#facebook a.btn' ).click( function( e ) {
e.preventDefault();
e.stopImmediatePropagation();
} );
};
var utilityFunctions =
{
disableSubmitButtons : function()
{
$( '#facebook input[type=submit]' ).prop( 'disabled', true );
}
}
Next I would modify the testing code like this:
it( "Disable all submit buttons", function() {
// Get a button
var $button = $( '#ai1ec_subscribe_users' );
var originalFunction = utilityFunctions.disableSubmitButtons;
utilityFunctions.disableSubmitButtons = function()
{
// call the original code, and follow it up with the test
originalFunction();
// check that all submit are disabled
$( '#facebook input[type=submit]' ).each( function( i, el ) {
console.log( 'f' );
expect( el ).toHaveProp( 'disabled', true );
});
// set things back the way they were
utilityFunctions.disableSubmitButtons = originalFunction;
}
// Call the function
utility_functions.block_all_submit_and_ajax( $button.get(0) );
});

Related

How to detect the dismissal of a bootstrap modal dialog in javascript?

I am trying to work with the bootstrap modal dialog. The events I am using are hidden and show. I have no problem using the show event. I don't understand how to use the hidden event on the other hand. I am displaying a form in the modal dialog and on the submit event of the form, I am hiding the modal dialog with $('.modal').modal('hide'). This hide event is also fired when the modal is dismissed either by using the close icon, by clicking an abort button which has this markup <button type="button" class="btn btn-default" data-dismiss="modal">Abort</button>, by pressing the escape key or by clicking somewhere on the .modal-backdrop. How can I distinguish a successful form submission from a dismissal of the dialog?
Since Bootstrap 3:
$('#yourModal').on('hidden.bs.modal', function () {
// code here
});
I have solved this issue in a slightly hacky way: When the form is submitted, i change the value of a custom data attribute of an element that is not part of the form being submitted. When the hidden event fires, I compare the values of my custom data attribute with the current value of the input element in the form that was displayed in the modal. If the two values differ, the modal has been dismissed, otherwise it was submitted.
$( '#modalWithForm' ).on( 'submit', 'form', function ( e ) {
e.preventDefault();
$.ajax( {
url: $( this ).attr( 'action' ),
method: 'POST',
data: {
param: parseInt( $( '#input' ).val(), 10),
}
} ).done( function ( ) {
$( 'label[data-custom]' ).data( 'custom', $( '#input' ).val() );
$( '#modalWithForm' ).modal( 'hide' );
} );
} );
$( '#modalWithForm' ).on( 'hidden.bs.modal', function () {
var modalDismissed = parseInt( $( '#input' ).val(), 10 ) !== parseInt( $( 'label[data-custom]' ).data( 'custom' ) );
$.ajax( {
url: '/Update',
method: 'POST',
dataType: "text",
data: {
param: parseInt( modalDismissed ? $( 'label[data-custom]' ).data( 'custom' ) : $( '#input' ).val(), 10 )
}
} ).done( function ( updatedForm ) {
$('form').empty().html(updatedForm);
} );
} );

Adding selectmenu on select that's calling functions

I have a script that loads html content to a div and applies jquery tabs at the same time. However, I want to get JQuery Selectmenu on my select at the same time.
I'm having trouble figuring out how to nest these.
I'll be continuing looking at the API Docs, and tutorials, stackoverflow etc.
BUT, In the meantime, I thought someone could help expedite the process.
This is my script as is:
$(function() {
var work = $( "#display" );
$( "#selector" ).change(function( event ) {
work.load($(this).val(),function(){
$("#textdisplay").tabs();
});
});
});
This script works just like i want it to, but It doesn't get styled with my theme because it's not a selectmenu
I want my select to use selectmenu:
$(function() {
$( "#selector" ).selectmenu();
});
Attempt 1:
$(function() {
var work = $( "#display" );
$( "#selector" ).selectmenu(
$( "#selector" ).change(function( event, ui ) {
work.load($(this).val(),function(){
$("#textdisplay").tabs();
);
});
});
});
Attempt 2:
$(function() {
var work = $( "#display" );
$( "#selector" ).selectmenu({
change: function( event ) {
work.load($(this).val(),function(){
$("#textdisplay").tabs();
});
});
});
});
Attempt 3:
$(function() {
var work = $( "#display" );
$( "#selector" ).selectmenu({
change: function( event, ui ) {
work.load($(this).val(),function(){
$("#textdisplay").tabs();
});
});
});
});
Attempt 4:
This attempt loads the selectmenu theme, but kills functionality
$(function() {
$( "#selector" ).selectmenu();
});
$(function() {
var work = $( "#display" );
$( "#selector" ).change(function( event ) {
work.load($(this).val(),function(){
$("#textdisplay").tabs();
});
});
});
Attempt 5:
$(function() {
var work = $( "#display" );
$( "#selector" ).selectmenu ({
selectmenuchange: function( event, ui ) {
work.load($(this).val(),function(){
$("#textdisplay").tabs();
});
}
});
});
So, i went back to the Jquery Documentation and found the correct syntax to make this work. I also learned a little more about how to use the console tab in developer tools view to track down syntax errors.
$(function() {
var work = $( "#display" );
$( "#selector" ).selectmenu ({
change: function( event, data ){
work.load($(this).val(),function(){
$("#textdisplay").tabs();
});
}
});
});

Validation Form Replicating

I have a form that when is submitted it checks if the user selected the field, and if didn't it gives a message to fill, but if i keep clicking the message keeps duplicating, triplicating and goes on... How can i make the message appear only one time? Here is the code
$( "#myform" ).submit(function( event ) {
var met = $("#mySelect").val();
if (met === "0"){
$( ".msn" ).append( "Select a Field" );
return false
}else{
$( "#myform" ).submit();
}
});
Since the problem is on the appended label try this:
$( "#myform" ).submit(function( event ) {
var met = $("#mySelect").val();
if (met === "0"){
// Clear .msn
$(".msn").empty();
// Append label
$( ".msn" ).append( "Select a field" );
return false
}else{
$( "#myform" ).submit();
}
});
Check this updated fiddle.
Use .one function
$( "#myform" ).one('submit', function( event ) {
var met = $("#mySelect").val();
if (met === "0"){
$( ".msn" ).append( "Select a Field" );
return false
}else{
$( "#myform" ).submit();
}
});

jQuery Validate multiple fields on form submit

I am trying to validate multiple fields in a form submit with jQuery, but I cannot get it work. Can someone help me with this?
// Validate checkout fields submit
$( '#checkout-data' ).submit( function( event ) {
$.each( '#checkout-data .required', function(){
if( this.value.length < 1 ) {
$( this ).removeClass( 'valid' ).addClass( 'invalid' );
}
});
event.preventDefault();
});
$( '#checkout-data' ).submit( function( event ) {
$.each( '#checkout-data .required', function(){
if( this.value.length < 1 ) {
$( this ).removeClass( 'valid' ).addClass( 'invalid' );
}
});
if ($('#checkout-data .required.invalid').length) {
event.preventDefault();
alert("invalid");
}
});

Calling a jQuery function multiple times

So, I have this function in jQuery:
$(function(){
$( ".unfocused" ).click(function ClickHeader () {
$( this ).addClass( "focused" );
$( this ).removeClass( "unfocused" );
$(".header").not(this).addClass( "unfocused" );
$(".header").not(this).removeClass( "focused" );
});
});
It works perfectly when a header is clicked the first time, but when I try to click another unfocused header, the function doesn't work anymore. Is it because it runs on document .ready?
Thanks for your help!
Change it like this:
$( document ).on("click", ".unfocused", function() {
$( this ).addClass( "focused" );
$( this ).removeClass( "unfocused" );
$(".header").not(this).addClass( "unfocused" );
$(".header").not(this).removeClass( "focused" );
});
This basically registers the event on the document. When you click a header, the event bubbles up to the document. There, the given selector is validated and the function is executed if needed.
Here is a jsfiddle using the delegate operation for handling the event like you need.
http://jsfiddle.net/MN9Zt/2/
$("body").delegate(".unfocused", "click", function() {
$(this).addClass("focused");
$(this).removeClass("unfocused");
$(".header").not(this).addClass("unfocused");
$(".header").not(this).removeClass("focused");
});

Categories

Resources