Jasmine: Creating DOM elements efficiently

Jasmine specs for the frontend often need some DOM elements to work with. Because creating them is such a common task, we should have an efficient way to do it.

Let's say I need this HTML structure:

<ul type="square">
  <li>item 1</li>
  <li>item 2</li>
</ul>

This card compares various approaches to fabricating DOM elements for testing.

Constructing individual elements

While you can use standard DOM functions to individually create and append elements, this is extremely verbose:

let list = document.createElement('ul')
list.type = 'square'
jasmine.fixtures.appendChild(ul)
let item1 = document.createElement('li')
item1.innerText = 'item 1'
list.appendChild(item1)
let item2 = document.createElement('li')
list.appendChild(item2)

For a reader it is hard to follow which DOM structure is being created.

Setting { innerHTML }

An alternative is to set the { innerHTML } property of a container element. This approach makes it very clear which DOM structure is being created:

jasmine.fixtures.innerHTML = `
   <ul type="square">
    <li>item 1</li>
    <li>item 2</li>
  </ul>
`

Note

I'm using a global jasmine.fixtures container, which is cleared automatically after each test.

One drawback is that you don't get variable references for the individual elements. For this you need to rediscover elements after setting { innerHTML }:

jasmine.fixtures.innerHTML = ...
let list = jasmine.fixtures.children[0]
let [item0, item1] = list.querySelectorAll('li')

Creating elements from CSS selectors

A quick way to fabricate DOM elements is to have library create them from a CSS selector. There are several libraries that let you pass a selector like "span.foo" and turn it into a <span class="foo"> element for you:

Here is the example above using Unpoly's up.element.affix() Show archive.org snapshot function:

let list = up.element.affix(jasmine.fixtures, 'ul[type=square]')
up.element.affix(list, 'li', text: 'item 1')
up.element.affix(list, 'li', text: 'item 2')

Note how every call to up.element.affix() returns a reference to the newly created element. I do not need to re-discover the individual elements after creation.

One more refinement

I often define a function jasmine.fixture() that takes a CSS selector and appends a matching element to my jasmine.fixtures container:

jasmine.fixture = function(...args) {
  let root = args[0] instanceof Element ? args.shift() : jasmine.fixtures
  return up.element.affix(root, ...args) 
}

This lets me shorten the example some more:

let list = jasmine.fixture('ul[type=square]')
jasmine.fixture(list, 'li', text: 'item 1')
jasmine.fixture(list, 'li', text: 'item 2')
Henning Koch Over 1 year ago