Search code examples
jqueryember.jsqunit

Simulating user input in Ember component tests


I'm trying to write tests for an ember component that simulate real user input. For example:

<div>
  <input class='js-input' type='text' value='{{ bar }}'> </input>
  <button class='js-save' type='submit' {{action 'save'}}>
</div>

My component currently uses the change and keyUp events to compute another value based on what's entered, and also to validate the input on-the-fly:

import Ember from 'ember';

export default Ember.Component.extend({
  bar: null,
  modelBar: null,

  updateBar: Ember.on('change', 'keyUp', function() {
    let bar = this.get('bar');
    if (bar.get('notValid')) {
      bar = null;
      this.set('bar', '');
    }
    this.set('modelBar', bar);
  }),

  actions: {
    save() {
      ... save stuff ...
    }
  }
});

So I've been using $('.js-input').val('some new value') to simulate this (as recommended here, under "Interacting with Rendered Components").

test('Updates a thing', function(assert) {
  assert.expect(1);

  const newState = 'a new state';

  this.set('actions.save', (newTaxes) => {
    assert.ok(true, 'save has been called');
    assert.equal(newState, this.get('modelBar'), 'model is updated correctly');
  });

  this.set('bar', 'initial state');

  this.render(hbs`
    {{my-component
      baz=baz
    }}
  `);

  this.$('.js-input').val(newState);
  this.$('.js-input').trigger('change');

  this.$('.js-save').click();     
});

However, when I run the test, the change event does not get the updated value for the input using this.get('bar') (although I can see it if I use this.$('js-input').val()). If I add an observer, I can see the observer get the updated value for the property but only after the custom change event fired.

I've tried wrapping things in Ember run loops and run.next loops and that hasn't helped either. Is there a way to make this work, hopefully without needing to fall back onto observers? (The component previously used an observer but some new requirements have made things more complicated.)


Solution

  • I found a couple of ways to fix this, depending on your level of comfortableness. Unfortunately, neither of them involved changes simply to the tests themselves, so I'd still be happy to hear if anyone has a better solution.

    One: Fix the problem jquery causes with more jquery:

    updateBar: Ember.on('change', 'keyUp', function() {
      ... validate bar ...
      this.set('modelBar', this.get('bar') || this.$('.js-input').val());
    }),
    

    I'm kind of OK with this since it should only be for testing purposes. You can worry about bar having a previous value, but in my case it was always going from empty to a value so || was a sufficient indicator.

    Two: Go back to an observer (sigh). It seems that although the change event does not get the updated value of bar, the observer does, but only after the change event.

    observerBar: Ember.observer('bar', function() {
      if (!this.get('suspendObserver')) {
        this.set('suspendObserver', true);
        this.updateBar();
        this.set('suspendObserver', false);
      }
    }),
    

    The semaphore was necessary in my case because updateBar would clear out bar if the input was invalid.