Wednesday, March 12, 2014

Keyboard Input Simulation in Polymer Elements


And, I'm back. I left off with some simple Karma and Jasmine testing of Polymer, so tonight, I try to pick back up with a similar question. How does interaction testing work with Polymer elements?

This question is part of the larger question of how do I test external mutation observers—specifically in the context of the chapter from Patterns in Polymer. I can feel myself sliding down a rabbit whole already with that one, so I set it aside for today. For now, I just want to test that user events like typing a name into an <input> field or pressing a button in a Polymer element has some kind of effect:



I already know that I can query the <h2> element of this simple <hello-you> Polymer with a Jasmine test along the lines of:
  describe('defaults', function(){
    it('says an impersonal hello', function() {
      var el = document.querySelector('hello-you');
      var h2 = el.shadowRoot.querySelector('h2');
      expect(h2.textContent).toEqual('Hello ');
    });
  });
So testing the expectation that the <h2> tag is updated is trivial, but how do you “type” in a Jasmine test such that Polymer will see the changes?

It turns out not to be a simple matter of updating the value of the <input>:
  describe('typing', function(){
    var el;
    beforeEach(function(){
      el = document.querySelector('hello-you');
      var input = el.shadowRoot.querySelector('input');
      input.value = 'Bob';
    });

    it('says an inpersonal hello', function() {
      var h2 = el.shadowRoot.querySelector('h2');
      expect(h2.textContent).toEqual('Hello Bob');
    });
  });
I am correctly targeting the proper <input> field as debugger…ing(?) proves that the value is updated, though Polymer does not recognize it:



I wind up having to create a Textinput event:
  describe('typing', function(){
    var el;
    beforeEach(function(){
      el = document.querySelector('hello-you');
      var input = el.shadowRoot.querySelector('input');
      // input.value = 'Bob';
      var e = document.createEvent('TextEvent');
      e.initTextEvent(
        'textInput',
        true,
        true,
        null,
        'Bob'
      );
      input.dispatchEvent(e);

    it('says an inpersonal hello', function() {
      var h2 = el.shadowRoot.querySelector('h2');
      expect(h2.textContent).toEqual('Hello Bob');
    });
  });
The initTextEvent() arguments are the usual init-event suspects: the name of the event (“textInput”), if the event bubbles, if it is cancelable, the associated view, and the event data. In this case, I use the string “Bob” to indicate that a user has typed in the name Bob. After dispatching the event, I have:
SUCCESS <hello-you> defaults says an impersonal hello
SUCCESS <hello-you> typing says a friendly hello 
So I have my passing test. And a new chain.

Day #1

2 comments:

  1. Hm, interestingly this sometimes does not set the value in my tests. I see in the browser that the field doesn't change. Any ideas?

    ReplyDelete
    Replies
    1. I've done this with success when I found similar issues:

      beforeEach(function(done){
      /* Init event as in the post... */
      input.dispatchEvent(e);
      setTimeout(done, 200);
      });


      Hope that helps!

      -Chris

      Delete