DEV Community

Cover image for Developing and Testing Web Components
Andrew Beng for Factorial GmbH

Posted on • Originally published at factorial.io

Developing and Testing Web Components

This blog is a follow up from my previous article "Custom Elements with Vue Reactivity". Here I will be sharing my thoughts on the challenges I faced setting up development and unit testing environments, while not forgetting about accessibility concerns for a Web Components project.

Thinking about Developer Experience

As Vue.js developers, we are spoiled when it comes to developer experience (DX), or “the experience developers have while using or working on your product”. From first-class documentation, to browser extension based or standalone Vue developer tools, to opinionated packages for routing, state management and unit testing, Vue’s ecosystem has always been developer centric, and newcomer friendly. Good DX is therefore a key value software project maintainers should work towards for long-term sustainability of their projects or their developer community.

When experimenting with Custom Elements in developing vue-uhtml, the lack of (or at least the lack of documentation of) developer tooling with decent DX became quickly apparent. To date, there is now default or opinionated out-of-the-box solution for web developers to spin up a basic development server to serve vanilla HTML, CSS and JavaScript - the underlying web technologies Custom Elements builds upon. I chose to scope the developer experience considerations for the vue-uhtml project to three areas:

  1. How might developers use vue-uhtml to develop a UI library
  2. How might developers unit test components built with vue-uhtml
  3. How might developers be aware of accessibility issues when developing with vue-uhtml

A dev environment for Custom Elements projects

Storybook is a popular and easy-to-use open source tool for building UI components in isolation. Given my previous experience in using Storybook for Vue based projects, this was a natural option for UI development with vue-uhtml. Storybook’s Web Components flavour made this decision an even easier one since this is officially supported and well documented. One possible drawback when using Storybook for Web Components is the use of the lit-html templating utility to create “stories” for your components. The lit-html project is incidentally the library Evan You chose to implement his own Custom Elements project with. In the case of this project, having to implement components in a slightly different uhtml syntax and “stories” in the lit-html syntax is objectively a negative developer experience, and has to be considered further for a wider vue-uhtml developer community.

A major hurdle to overcome when developing Custom Elements is an approach for implementing styles and CSS via the encapsulated Shadow DOM, especially when also using CSS pre and post processors. This issue is usually solved during the build process when tools like Webpack or Rollup orchestrate CSS transformations before bundling and injecting processed styles in the right place. Front-end framework CLI tools like vue-cli-service, Vite or create-react-app abstract much of the underlying processing and building for both development and production environments. For example, most Vue developers would not need to be concerned with how a Vue single file component gets compiled by Webpack or Vite and served on a local port when yarn serve is run. This is great DX, but probably not something that would be easily achievable for vue-uhtml development in the short term.

Putting the pieces together, these were the steps I took to set up a Storybook based development environment with decent DX to build UI components with vue-uhtml:

  1. Configure a bare bones Rollup project to process vue-uhtml defined Custom Elements for both development and production
  2. Configure Rollup to watch the src directory to compile the components to a dev directory
  3. Configure Storybook for Web Components and write component stories using the rolled-up output component definitions in the dev directory (rather than src)
  4. Run Rollup and Storybook concurrently via a dev script
// UiCheckBox.stories.js
import { html } from "lit-html";
import { defineUiCheckBox } from "../../dev/index.esm";

defineUiCheckBox();

export default {
  title: "Components/UiCheckBox",
};

const Template = () => html`<ui-check-box></ui-check-box>`;

export const Default = Template.bind({});
Enter fullscreen mode Exit fullscreen mode

The trial and error in setting up these steps could in the future be the basis for vue-uhtml’s own CLI, or to extend or use Vite for a similar purpose.

Unit testing Custom Elements

The next consideration I addressed was being able to easily write and run unit tests for vue-uhtml components. Having previously talked about a unit testing approach for Vue.js components, I was keen to apply the principle of “writing tests that assert your component’s public interface”. Before I could even think about writing component tests, I had to pick the most appropriate and well supported tooling. Vue’s documentation on testing and own vue-test-utils package makes this choice a foregone conclusion with Jest as a comprehensive test framework, and using vue-test-util’s wrapper based API to mount components in an isolated jsdom environment. While jsdom has provided a Custom Elements implementation since version 16, other Web Components APIs including Shadow DOM are not supported or reliable enough to be useful.

Since Custom Elements is a browser based web technology, a common approach to work around jsdom’s limitations is to consider using Puppeteer to run tests on Custom Elements rendered in real browsers. However, in practice this usually isn’t the most practical solution as “boilerplate” code is required to navigate to the components on a served HTML page.

To improve DX and streamline the unit testing approach for Custom Elements, the folks at Open Web Components have provided an opinionated testing package aptly called @open-wc/testing that “combines and configures testing libraries to minimize the amount of ceremony required when writing tests”. While not having a choice of test runner or assertion library (@open-wc/testing uses Mocha and Chai respectively with Puppeteer) may seem restrictive, the positive trade-off is clear documentation and concise examples to help get up and running writing component tests.

Potential gotcha: shadowRoot.mode

The encapsulation of Custom Element features is aided by the ability to set a component’s shadowRoot mode to ”closed”, preventing the shadow root's internal features being accessible from JavaScript. This is a feature that is set to “closed” by default when building components with vue-uhtml. This has potential “gotcha” implication in preventing test scripts from asserting the components’ internal features. To prevent falling into this trap, components based on Custom Element should easily allow developers to be defined with an ”open” shadow root mode when used in tests.

The example below outlines tests for a custom checkbox component which:

  1. Asserts that the initial checked value is false
  2. Asserts that the checked value is true if its attribute is passed
  3. Asserts that the checked value is true after the input is clicked
import { html, fixture, expect, elementUpdated } from "@open-wc/testing";

import { defineUiCheckBox } from "../../dist/index.esm";
defineUiCheckBox({ isTest: true });

describe("UiCheckBox", () => {
  it("has a default checked value which is falsy", async () => {
    const el = await fixture(html` <ui-check-box></ui-check-box> `);

    expect(el.checked).to.not.be.ok;
  });

  it("has a checked of true when the 'checked' attribute is passed", async () => {
    const el = await fixture(html` <ui-check-box checked></ui-check-box> `);

    expect(el.checked).to.equal(true);
  });

  it("has checked value after click", async () => {
    const el = await fixture(html` <ui-check-box>Checkbox</ui-check-box> `);

    expect(el.checked).to.not.be.ok;

    el.shadowRoot.querySelector("input").click();

    await elementUpdated(el);

    expect(el.checked).to.be.ok;
  });
});
Enter fullscreen mode Exit fullscreen mode

Developing with a11y in mind

Web accessibility (a11y) has historically been an area overlooked by developers and designers alike, often seen as more of an art than precise science. While there is no single tool or methodology that solves all a11y concerns for web development, significant progress in tooling has made it possible to integrate Web Content Accessibility Guidelines (WCAGs) into development workflows. Although the use of such developer tools does not solve the actual task of authoring accessible websites and apps, they do help developers become more aware of the a11y topic during development rather than after. One such tool at the forefront of this drive to assist developers in developing with a11y is axe, which provides an open-source accessibility rule set for automated accessibility validation. The axe rule set can in turn be used in a11y validation plugins in other developer tools. I was able to easily integrate an axe based a11y plugin for Chai with @open-wc/testing into my vue-uhtml components project.

The axe plugin for Chai allows developers to automatically run a11y validation as part of their TDD or BDD workflow.

await expect(el).to.be.accessible();
Enter fullscreen mode Exit fullscreen mode

Take-aways

Thinking about developer experience in the context of developing a Custom Elements based project has been a lot like thinking about user experience when developing front-end websites and applications. Instead of a visual user interface, a tool/library/framework’s API is the end user’s (in this case developer’s) medium for achieving their tasks. This small exercise in DX has made me appreciate the thoughtful contributions open source maintainers make in keeping their tools and documentation easy to use for the wider developer community.

Links

Top comments (0)