Testing an Ember select2 component with page objects

Image courtesy of Flickr user https://www.flickr.com/photos/dalbera/

select2, “the jQuery replacement for select boxes” is great for making better select controls. Like most jQuery libraries, it’s pretty easy to wrap it a component and start using it in an Ember application. However, writing acceptance tests is not so simple: select2 creates its own DOM elements and expects events which don’t play nice with Ember’s built-in test helpers. Putting these messy details in a page object makes acceptance tests easier to read, write, and understand.

select2 component for Ember

select2 is available as a bower depedency (bower install select2). I wrapped it a simple Ember component for use in my application. It does two key things:

1. initialize select2 when the component element is ready (on didInsertElement)

Ember.$('#'+this.get('selectId')).select2(config);

2. send an action when the user changes something, using select2’s select2:select and select2:unselect events:

    $select.on('select2:select', () => {
        this.sendAction('action', $select.val(), 'select');
    });
    $select.on('select2:unselect', () => {
        this.sendAction('action', $select.val(), 'unselect');
    });

Now, I can wrap a standard select element with the select2-select component, and it will magically be upgraded as the page renders. For example:

{{#select2-select
    action="onChangeSelect"
    config=config
    items=items
    selectId="mySelect"}}
        <select id="mySelect" multiple>
            {{#each items as |item|}}
                <option value="{{item.id}}" selected={{item.selected}} data-label="{{item.label}}">
                    {{item.label}} ({{item.count}})
                </option>
            {{/each}}
        </select>
{{/select2-select}}

The complete component is available as an Ember addon from Github at ember-select2-with-test-helper.

Acceptance testing and page objects

The goal of an acceptance test is to verify application flow. Like a real user, these tests are not concerned with the internal details of how a component works. Ember has a number of built-in test helpers that abstract away some of the details involved in performing common user interactions. However, with a complex jQuery-driven component like select2, sometimes a test author needs more fine-grained access to the DOM.

Acceptance tests should be readable and easy to write. Using long jQuery selectors and multi-step sequences to trigger events is repetitive, error prone, and distracting. The page object pattern helps solve exactly this problem. The steps to drive the page elements go in a separate object with a nice API that can be re-used across any number of tests.

To get started writing page objects, I installed the ember-cli-page-object addon. In addition to providing a number of handy helpers, this addon makes it easy to start using page objects because it’s not all or nothing. Tests can use page objects where they bring the biggest benefit, and older tests do not need to be re-written.

Select2 test helpers

The most basic thing my test needs to do with a select2 component is select and de-select items. In my implementation, I set the title attribute on each option with a simple description of the option, because the option may contain extra metadata for display. Since select2 hides the underlying standard select, I couldn’t just set the value of the select element. Through trial and error, I found that I needed to click the select2 input element, then trigger a mouseup on the option I wanted. The select2 DOM elements are added to the end of the page, outside the test context. The PageObject helpers, like the Ember helpers, scope the selectors to the test context. I used standard jQuery to find the elements:

selectByTitle(title) {
    $('.select2-container input').click();
    $('.select2-results li[title="%@"]'.fmt(title)).trigger('mouseup');
},
deselectByTitle(title) {
    $('.select2-selection__choice[title="%@"] .select2-selection__choice__remove'.fmt(title)).click();
},

After selecting an option, my test needs to verify two different things. First, selected options should no longer appear in the available options. To do this, I open the drop down, count the number of visible options, then close it. As before, I used jQuery instead of PageObject helpers to access the select2 results outside the testing container:

selected items
displayedOptionCount() {
    $('.select2-container input').click();
    let count = $('.select2-results__options li:visible').length;
    $('.select2-container input').click();
    return count;
},
selectedCount: PageObject.count('.select2-selection__choice'),

One of the nice features select2 has is the ability to add new options, with the `{ tags: true }` configuration option. If this is enabled, a user can type in the box, click the row in the dropdown, and the new item will be added to the selections. This looks a little different to a test: enter a value in the input, trigger a search event to tell select2 to search for the text, then trigger a mouseup to select the new item and add it to the selection. A subset of this interaction is entering text and searching, but not actually selecting the new option. This is useful for tests where tags are not enabled in the select2 component.

add tag
addTag(value) {
    $('.select2-container input').val(value);
    $('.select2-search--inline').trigger({type: 'input.search'});
    $('.select2-results li:first').trigger('mouseup');
},
typeTag(tag) {  // type but don't select
    $('.select2-container input').val(tag);
    $('.select2-search--inline').trigger({type: 'input.search'});
},

After typing into the select2 input box, the component displays either a list of options that match the text, or a no results message. I added helpers to query the message and number of matches from a search. As before, these use jQuery instead of PageObject helpers to access results outside the testing container.

search
searchResultsText() {
    return $('.select2-results .select2-results__message').text().trim();
},
searchResultsCount() {
    return $('.select2-results__options li:visible').length;
},

Using the test helpers

Of course, my pages contain more than just a single select2 component, and many different pages may contain a select2 component. I put the helpers above in a page object so that they could easily be added to any test that needs them. As an example, I generated a page object and added the select2 test helpers to it:

import Select2Helper from './select2-helper';

let {
    visitable, count
} = PageObject;

let Demo = {
    visit: visitable('/:page'),
    // ... more methods here
};

export default PageObject.create(Object.assign(Demo, Select2Helper));

Now, my accepance test can use the page object to concisely and easily drive the select2 component. This simple acceptance test gets the number of displayed options, selects an option, adds a new option, and verifies the number of selected options:

test('visiting /with-tags', function(assert) {
    page.visit({page: 'with-tags'});

    andThen(function() {
        assert.equal(page.selectedCount, 1, '1 selected');
        // the select2 helper doesn't know about the hidden select element
        assert.equal(find('#selectWithTags option').length-1, page.displayedOptionCount(), 
            'selected item not displayed');
        page.selectByTitle('Cantaloupe');
    });

    andThen(function() {
        assert.equal(page.selectedCount, 2, '2 selected');
        page.addTag('Watermelon');
    });

    andThen(function() {
        assert.equal(page.selectedCount, 3, '3 selected');
        page.deselectByTitle('Cantaloupe');
    });

    andThen(function() {
        assert.equal(page.selectedCount, 2, '2 selected');
    });
});

Conclusion

It’s easy to throw some code around a jQuery component and make it work with Ember. However, it’s not always so easy to test this component; it may require complex, messy DOM interactions that aren’t well-supported by Ember’s built-in test helpers. Putting this code into a page object makes it reusable across any number of tests. Test authors only need to study the internals of the component once; after that they can just include the test helpers and get on with writing a test that’s easy to read and understand.  

Many Ember addons include tests for their own functionality, but don’t provide a way to leverage this work in another application (other than cut and paste, anyway). Page objects can help with this too: put a set of test helpers inside an Ember addon’s test-support directory, and it will be copied to the consuming application’s tests directory on install. This makes the helpers available for applications to use in their own acceptance tests.

The complete code for the select2 component wrapper and test helper, plus additional sample tests, are available from ember-select2-with-test-helper.

About Kimberly Nicholls

Kimberly Nicholls is a full stack engineer at Gridium who loves to make data useful. She also enjoys reading books and playing outside, especially in the water.

0 replies on “Testing an Ember select2 component with page objects”

You may also be interested in...

Measuring Aurora query IO with Batch experiments
Measuring Aurora query IO with Batch experiments

Running production-scale tests with AWS Batch showed that using SQLAlchemy to modify only changed rows reduced our Storage IO costs, as well as the accumulation of dead tuples in our database.

Migrating to Aurora: easy except the bill
Migrating to Aurora: easy except the bill

Pay for usage sounds good, but how you use the database is key. Migrating our production database from Postgres to Aurora was easy, until we noticed that our daily database costs more than doubled. Our increased costs were driven by…

Fresh baked software
Fresh baked software

In the olden days, software used to come in a box. Now, we can make sure our users always have fresh baked software.