All source can be found at: Aurelia-Aire
Testing UI components is one of the more difficult parts of QA. In Stratosphere, we’d been using Karma + Jasmine, but the browser component had been complicated by the fact that providing a DOM to tests in a fast, portable manner subject to memory and speed constraints can be pretty challenging. Sure, initially we did the PhantomJS thing, then Chrome Headless came out, but writing UI tests just never really felt natural.
Then, last week, we decided to open-source our component framework, Aire, built on top of UIKit+Aurelia, and that created an exigent need to fix some of the things we’d been limping along with, most importantly testing. The success of OSS projects depends on quite a few things, but I consider providing a simple way to get contributors up-and-running critical.
Internally, Aurelia uses an abstraction layer (Aurelia PAL) instead of directly referencing the browser’s DOM. Aurelia will (in principle) run on any reasonable implementation of PAL. Aurelia provides a partial implementation OOTB, Aurelia/pal-nodejs, that will enable to (mostly) run your application inside of NodeJS.
Our project structure is pretty simple: we keep all our components and tests under a single directory,
aire ├── build │ └── paths.js ├── gulpfile.js ├── index.html ├── jest.config.js ├── jspm.config.js ├── jspm.dev.js ├── package.json ├── package-lock.json ├── src │ ├── main │ │ ├── aire.ts │ │ ├── application │ │ ├── button │ │ ├── card │ │ ├── core │ │ ├── core.ts │ │ ├── dropdown │ │ ├── events.ts │ │ ├── fab │ │ ├── form │ │ ├── icon │ │ ├── init │ │ ├── init.ts │ │ ├── loader │ │ ├── nav │ │ ├── navbar │ │ ├── offcanvas │ │ ├── page │ │ ├── search │ │ ├── table │ │ ├── tabs │ │ └── widget │ └── test │ │ ├── button │ │ ├── core │ │ ├── init │ │ ├── render.ts │ │ ├── setup.ts │ │ └── tabs ...etc
At the top of the tree you’ll notice
jest.config.js, the contents of which look like this:
Basically, we tell Jest to look under
src for everything.
ts-jest will automatically look for your Typescript compiler configuration,
tsconfig.js in its current directory, so there’s no need to specify that.
tsconfig is pretty standard for Aurelia projects:
If you just copy and paste our
jest.config.js files while following the outlined directory structure, everything will Just Work (don’t forget to
npm i -D the appropriate Jest and Aurelia packages.)
At this point, you can use
aurelia-test to write tests a la:
Now, you can run your tests with
email@example.com test /home/josiah/dev/src/github.com/sunshower/aurelia-aire/aire npx jest PASS src/test/button/button.spec.ts PASS src/test/tabs/tab-panel.spec.ts PASS src/test/init/init.spec.ts PASS src/test/core/dom.spec.ts Test Suites: 4 passed, 4 total Tests: 12 passed, 12 total Snapshots: 0 total Time: 3.786s Ran all test suites.
Enabling support for complex DOM operations
ReferenceError: is not defined error. Moreover, if we changed the Jest environment from
jsdom, we’d encounter a variety of errors along the lines of
TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node' which I suspect were caused by
pal-nodejs creating DOM elements via its own
jsdom dependency while Jest was performing DOM operations using its
jsdom dependency. In any case, the solution turned out to be to define a single, global
jsdom by importing
jsdom-global. Once we discovered this, we encountered other issues with browser-environment types and operations not being defined, but this
setup.js resolved them:
At this point we could successfully test Aurelia + UIKit in NodeJS.
The final piece
All of our component views are developed in Pug, and I didn’t like that we’d be developing in Pug but testing using HTML. The solution to this turned out to be pretty simple: adding Pug as a development dependency and creating a pretty simple helper function:
With that final piece in place, our test looks like:
The benefits of writing tests in this way became apparent the moment we cloned the project into a new environment and just ran them via
npm run test or our IDE’s. They’re fast, don’t require any environmental dependencies (e.g. browsers), and allow you to run and debug them seamlessly from the comfort of your IDE. But, perhaps most importantly, these are fun to write!