javascriptangularjsjqlite

Using element.triggerHandler() in angularjs tests for paste and keypress events doesn't seem to have an effect


Wondering if someone out there can shed some light on how to properly use element.triggerHandler() for the paste and keypress events inside of an angularjs unit test.

I have two directives, one for limiting the ability of a user to continue firing keypress events in an element once a length limit has been reached. The second is to prevent the user from pasting text into an element if the length of the text would exceed a limit.

See the following plunker for a full example including my failing tests: https://plnkr.co/edit/5Yyv2cnn3dRKzsj2Lj61?p=preview

For the paste test I know I'm not using the correct syntax but have been unable to find how to properly do this. Any suggestions?

element.triggerHandler('paste', 'astring')

For the keypress test, I believe I'm firing the event correctly but it doesn't seem to be updating the value of the element (retrieved using element.val())

Been stuck on this for a bit, any help would be greatly appreciated. Thanks!


Solution

  • Let's us start with a short breakdown of what might happen (really up to the browser implementation) when a user presses and releases the 1 key with the focus on an input:

    1. Event keydown is fired
    2. Event keypress is fired
    3. Value of input is changed to 1 and event input is fired
    4. Event keyup is fired

    There is no way in JS to actually simulate a user pressing a key. What you can simulate are the things that (usually) happen when a user does so, for example the steps above.

    The triggerHandler function executes all handlers bound with jQuery for the specified event type on the specific element.

    So using triggerHandler with keypress will not simulate a user pressing a key, it will only fire the event, like step 2 above. The value will not be changed, since that happens in another step.

    Now, one would think that in your tests for the limitKeypressLength directive, you can simply simulate the first part of step 3 above yourself (just setting the value manually):

    for (var i = 0; i < 10; i++) {  
      element.triggerHandler({type: 'keypress', keyCode: 49});
      element.val(element.val() + '1');
    }
    
    expect(element.val()).toBe('1111111111');
    
    element.triggerHandler('keypress', {which: 49});
    element.val(element.val() + '1');
    
    expect(element.val()).toBe('1111111111');
    

    This will not work however, since even if the eleventh keypress event is caught in your directive, the code below will still execute and update the value.

    The basic functionality of the limitKeypressLength directive is to listen on the keypress event and either call event.preventDefault or not based. This is what you want to test.

    For example:

    // Set value to something longer than allowed
    element.val('123456789123456789');
    
    // Create the event
    var e = jQuery.Event('keypress', {
      keyCode: 49
    });
    
    // Create a spy
    spyOn(e, 'preventDefault');
    
    // preventDefault should not have been called yet
    expect(e.preventDefault).not.toHaveBeenCalled();
    
    // Trigger the event
    element.triggerHandler(e);
    
    // Assert that preventDefault has been called
    expect(e.preventDefault).toHaveBeenCalled();
    

    Demo: https://plnkr.co/edit/ktmcBGSuTdMnvqVRlkeQ?p=preview

    Now you can as easily test for when the elements value is set to equal/below the allowed value.

    Basically the same goes for the limitPasteLength directive, since its purpose is also to call preventDefault based on a condition, only that there is some additional mocking to do.