Read more

Jasmine: Creating DOM elements efficiently

Henning Koch
November 17, 2022Software engineer at makandra GmbH

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.

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more Show archive.org snapshot

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
November 17, 2022Software engineer at makandra GmbH
Posted by Henning Koch to makandra dev (2022-11-17 09:29)