Testing with Dojo

dojo testing

When you use the Dojo cli to create an application, it comes with a full test framework for you to use. If like me, you aren’t as diligent with your tests as you maybe you could be, that’s ok, there’s no testing police that will give you a citation, only your own shame when you try to track down how you broke some feature after some updates you made. I kid, I kid. But, it is a good idea to have some tests written to make sure core features of your application don’t break as you write new code.

When you first create a Dojo application, it will already provide you with some unit tests that you can use as a guide.

Default Dojo Installation

You can find this sample repo on github.

Let’s see what a unit test looks like.

Running Tests

You can run these tests using npm test and you should get results like below.

Dojo testing results

So what does one of these tests look like?

// tests/unit/widgets/Profile.ts
const { describe, it } = intern.getInterface("bdd");
import harness from "@dojo/framework/testing/harness";
import { w, v } from "@dojo/framework/widget-core/d";

import Profile from "../../../src/widgets/Profile";
import * as css from "../../../src/widgets/styles/Profile.m.css";

describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, { username: "Dojo User" }));
    h.expect(() => v("h1", { classes: [css.root] }, ["Welcome Dojo User!"]));
  });
});

When testing widgets, you are basically testing that the output of the widget is what you expect, especially when given a set of properties. Ideally, rendering a widget is going to be pure function, meaning they should be pretty easy to test when given the same input.

The Dojo framework comes with a harness helper, which can be used to test your widgets. You can give it a widget, and check that the virtual DOM output is what you expect.

  • Does it render as expected?
  • Does a child widget or element render as expected?
  • Do event handlers work as expected?

Assertion Templates

The Profile widget we tested above has a property username that we can test against in the output. We could rewrite the entirety of the expected virtual DOM output for each test (that’s a lot of typing) or we could create an assertion to test against that would allow us to change the expected properties on each run.

Let’s see how that would work.

First, I need to update the Profile widget slightly since the username property is required. We can make it optional and provide a default value in the widget.

// src/widgets/Profile.ts
export interface ProfileProperties {
  username?: string;
}

export default class Profile extends WidgetBase<ProfileProperties> {
  protected render() {
    const { username } = this.properties;
    return v("h1", { classes: [css.root] }, [
      `Welcome ${username || "Stranger"}!`
    ]);
  }
}

This is a little safer anyway. Now in my test, I can create my assertion template.

// tests/unit/widgets/Profile.ts
// Add the assertionTemplate module
import assertionTemplate from "@dojo/framework/testing/assertionTemplate";
...

// Create my assertion
const profileAssertion = assertionTemplate(() =>
  v("h1", { classes: [css.root], "~key": "welcome" }, ["Welcome Stranger!"])
);

describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, {}));
    // Test against my base assertion
    h.expect(profileAssertion);
  });
});

We can test against our base assertion like we did before. In our assertion template, we add a ~key property to the node so that we can update its expected output. In a tsx file, this is called assertion-key.

We can now test the output if we provide a given property to the widget.

// src/tests/unit/widgets/Profile.ts
describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, {}));
    h.expect(profileAssertion);
  });

  it("renders given username correctly", () => {
    // update the expected result with a given username
    const namedAssertion = profileAssertion.setChildren("~welcome", [
      "Welcome Kel Varnsen!"
    ]);
    const h = harness(() => w(Profile, { username: "Kel Varnsen" }));
    h.expect(namedAssertion);
  });
});

What the ~key allows is for us to update that expected portion of our assertion template. So if I provide a username, I should expect a different welcome message. The assertionTemplate.setChildren() returns a new assertion template you can reference so that you don’t need to reset it after each unit test, which is incredibly useful and I think is a nice touch to the library.

You can read more about assertion templates and some of its other useful methods in the documentation.

Summary

This was just a quick look at testing with Dojo, but I think it highlights how useful the provided tools are for you test your widgets! Dojo tests use intern by default, so you can look at the docs on how to test the business logic of your applications as well. An added benefit here is that intern provides functional tests, so you can test the behavior of your application as a user would interact with it. This would require a blog post of its own, but you can look at the Dojo todo-mvc example to see how it uses functional tests.

Now I know everyone is going to go out and write unit tests for all their code!

Dojo from the Blocks

Dojo blocks

One of the low-key features that was released in Dojo 5 was the introduction of Blocks. Blocks go hand-in-hand with Dojo build time rendering.

What Blocks allow you to do is run some arbitrary code in a node environment during the build process.

Build time rendering is a great tool you can use to generate static content without having to worry about any server side component to generate pages as requested.

For example, you could use Blocks to preprocess images that you might want loaded into your page, or maybe a more common use case of converting markdown to use for you blog or site. Blocks give you the flexibility to run code you might normally run in the server environment during your build process.

Building a block

Maybe I want to build my blog on top of Dojo, and I want to just write my articles in markdown. I can use a library like showdown to parse my markdown files to HTML. Here is a very basic module that can do this.

// src/blocks/markdown.block.ts
import * as fs from 'fs';
import { resolve } from 'path';

import { Converter } from 'showdown';

const mdConverter = new Converter();

export default function (path: string) {
  path = resolve(__dirname, path);
  const file = fs.readFileSync(path, 'utf8');
  // convert Markdown to HTML
  const html = mdConverter.makeHtml(file);
  return html
};

Blocks are types of metas you can use in your widgets. I can use my block by calling the meta, and running it with with the needed arguments, like the path to the markdown file I want to parse.

import WidgetBase from "@dojo/framework/widget-core/WidgetBase";
import { dom } from "@dojo/framework/widget-core/d";
import Block from "@dojo/framework/widget-core/meta/Block";
import { tsx } from "@dojo/framework/widget-core/tsx";

import fromMarkdown from "../blocks/markdown.block";

import * as css from "./styles/About.m.css";

export default class About extends WidgetBase {
  protected render() {
    const node = document.createElement("div");
    // Use my block
    const message = this.meta(Block).run(fromMarkdown)(
      "../../markdown/post2.md"
    );
    node.innerHTML = message;
    // Create a vnode to inject my HTML
    const vnode = dom({ node });
    return (
      <div>
        <h1 classes={css.root}>About Page</h1>
        {vnode}
      </div>
    );
  }
}

I can now naively inject my parsed markdown as HTML into my page. Ideally, I would like to convert that HTML into real virtual dom nodes, but I haven’t gotten that far yet.

You can quickly see how useful this would be during build time to process files, maybe pull in some external files and use them in an app.

Image processing

In my app, I might have some images that I want to convert to base64 strings so I can embed them. I can use a tool like sharp to resize my images. When I do, I can go ahead create the virtual dom nodes and return them in my block.

// src/blocks/imagebase64.block.ts
import { resolve } from 'path';
import { v } from '@dojo/framework/widget-core/d';
import * as sharp from 'sharp';

export default async function (path: string) {
  path = resolve(__dirname, path);
  // resize my images
  const images = [
    await sharp(path).resize(200).toBuffer(),
    await sharp(path).resize(300).toBuffer(),
    await sharp(path).resize(400).toBuffer(),
    await sharp(path).resize(500).toBuffer()
  ];

  return images.map((a) =>
    v('img', { src: `data:image/jpeg;base64, ${a.toString('base64')}`, alt: 'sally' })
  );
};

You might notice, that I’m able to run asynchronous tasks inside my block. This allows me to do some more interesting things like image processing, fetching data, or maybe run some sort of analysis on a dataset to create formatted json that can be used by a charting library! I’m just throwing out some ideas here!

Summary

You can view the source code for this sample here, and you can view a live demo here.

Dojo Blocks are really interesting, and I think they provide a whole new level of functionality for developers taking advantage of build time rendering with Dojo. I don’t see a reason not to use build time rendering, and Blocks offer you a whole new opportunity to get crazy about it. I’m currently looking at a rewrite of my blog with Dojo using them!

Maintain State with Dojo Stores

Dojo Stores and Processes

We previously looked at how you could maintain state with Dojo containers using a context for your application. To recap, containers are basically higher order components that you can use to add state management to widgets.

Using a context with containers makes the process fairly simple, however if you want to wire up multiple containers and routes with shared state, you might want to start looking at using Dojo Stores.

Dojo Stores work by using Processes to execute Commands with varying Operations against your state. You don’t need to define your operations, Dojo provides them for you. These operations are based on the JSON Patch format. They currently support add, remove, replace, and test. If you’re not familiar with these operations or they look intimidating, don’t worry, they are much simpler than they may seem at first.

In order to try and grasp how everything works, let’s create a small application that explores the Star Wars API.

API Explorer Widget

First thing we’ll do is create a widget that can explore the API. I’ll use a Listbox to display the endpoints of the API and a TextArea to display the raw JSON results of the selected endpoint.

// src/widgets/APIExplorer.tsx
import { tsx } from "@dojo/framework/widget-core/tsx";
import { WidgetBase } from "@dojo/framework/widget-core/WidgetBase";
import { watch } from "@dojo/framework/widget-core/decorators/watch";
import Listbox from "@dojo/widgets/listbox";
import TextArea from "@dojo/widgets/text-area";
import theme from "@dojo/themes/dojo";
import * as css from "./styles/APIExplorer.m.css";

import { ExplorerProperties, Item } from "../interfaces";

export class APIExplorer extends WidgetBase<ExplorerProperties> {
  @watch() index = 0;
  @watch() label = "";

  onAttach() {
    this.properties.fetchAllResults();
  }
  protected render() {
    return (
      <div classes={css.root}>
        <Listbox
          theme={theme}
          key="listbox1"
          activeIndex={this.index}
          widgetId="listbox1"
          optionData={this.properties.items}
          getOptionLabel={(option: Item) => option.label}
          getOptionSelected={(option: Item) => option.label === this.label}
          onActiveIndexChange={(index: number) => {
            this.index = index;
          }}
          onOptionSelect={(option: Item, index: number) => {
            this.label = option.label;
            this.properties.fetchData(option);
          }}
        />
        <TextArea rows={15} theme={theme} value={this.properties.result} />
      </div>
    );
  }
}

// src/interfaces.ts
export interface ExplorerProperties {
  items: Item[];
  result: string;
  fetchAllResults: () => void;
  fetchData: (item: Item) => void;
}

This widget has some local state to manage the selected value of the Listbox, but other than that, it relies on the properties passed to it in order to display anything useful. I’ll be using a container to pass those properties to this widget. But first, how would I make my queries and actually update my application state in the first place?

Processes

Processes are used to execute commands against your application store. It’s in the processes that you are going to be doing most of your heavy lifting for your application. This is where you can do queries, transformations, filtering, validation, and so on. There are factory functions in Dojo to help you create your commands.

// src/processes/starwarsProcesses.ts
import {
  createProcess,
  createCommandFactory
} from "@dojo/framework/stores/process";
import { replace } from "@dojo/framework/stores/state/operations";

import { State, Item } from "../interfaces";

// commandFactory typed to my application state
const commandFactory = createCommandFactory<State>();

In this application, I’m going to use the replace operation to make my state updates. If I were adding items to an array in my commands, I could do some interesting things using the at helper and add operation to insert new items into an array at specific indices.

Let me create a command that will fetch all the available endpoints of the Star Wars API.

// src/processes/starwarsProcesses.ts
const fetchItems = commandFactory<Item>(async ({ path }) => {
  const response = await fetch("https://swapi.co/api/");
  const json = await response.json();
  const items: Item[] = Object.keys(json).map(key => {
    return {
      label: key,
      value: json[key]
    };
  });
  return [replace(path("items"), items)];
});

I use my command factory to create a function that will make my query and then returns an array of operations. Why any array? Because I may want a single command do multiple state updates. In this case I use [replace(path(“items”), items)]. This may look odd at first, but the path method is a way to designate a property path in my state. If I want to use the value from my state I could use a get helper and write get(path(“items”)), which would return the items array.

I was unsure how I felt about this method of accessing state at first, but it lends itself very well to a functional and reactive method of interacting with state that has really grown on me. The behavior is very much like working with lenses.

Now, I’ll create a command to fetch the results of one of the selected endpoints.

// src/processes/starwarsProcesses.ts
const fetchResult = commandFactory<Item>(async ({ path, payload }) => {
  const response = await fetch(payload.value);
  const result = await response.json();
  return [replace(path("result"), JSON.stringify(result, undefined, 2))];
});

Very similar to the previous command I am going to fetch the results of an endpoint, but that endpoint URL is coming from a payload object that was passed to my command. I’ve typed this payload to have a value property, but it will be any argument that has been passed to the process that will execute this command. We’ll see how that is done in a moment, but first we need to create a couple of processes that will be used to execute these commands.

// src/processes/starwarsProcesses.ts
export const fetchItemsProcess = createProcess("fetch-items", [fetchItems]);
export const fetchResultProcess = createProcess("fetch-result", [fetchResult]);

We create two processes to do two distinct operations on my state. What I find interesting here is that I can have a single process execute multiple commands that could in turn perform multiple operations against my state. I haven’t done this yet, but I find the fact that I could to be awesome!

Container

Let’s wrap our widget in a container that will use our processes to wire up properties and methods.

// src/containers/APIExplorerContainer.ts
import { Store } from "@dojo/framework/stores/Store";
import { StoreContainer } from "@dojo/framework/stores/StoreInjector";
import { APIExplorer } from "../widgets/APIExplorer";

import { State } from "../interfaces";

import {
  fetchItemsProcess,
  fetchResultProcess
} from "../processes/starwarsProcesses";

function getProperties(store: Store<State>): any {
  const { get, path } = store;
  
  return {
    items: get(path("items")),
    result: get(path("result")),
    fetchAllResults: fetchItemsProcess(store),
    fetchData: fetchResultProcess(store)
  };
}
// Use a StoreContainer
export const APIExplorerContainer = StoreContainer(
  APIExplorer,
  "state",
  { getProperties }
);

We are using a specific container called a StoreContainer that will inject our store in to our getProperties method. This method is how you can pass properties to your wrapped widget from the container.

You can see here that the store has helper methods, get and path, that I mentioned before to access the values of properties on the store. I can now pass processes that will execute commands against the store as methods that my widget is expecting as part of its properties.

Once this is done, I need to register my store with my application and inject it.

// src/main.tsx
const store = new Store();
const registry = registerStoreInjector(store);

class App extends WidgetBase {
  protected render() {
    return <APIExplorerContainer />;
  }
}

const r = renderer(() => <App />);
r.mount({ registry });

I use a helper called registerStoreInjector and then inject that store into a named state that I used in my container.

What you end up with is an application like this one.

Summary

There is a lot happening here, but what it boils down to is the following steps.

  • Create processes to execute commands against a Store
  • Wrap widget in a container to pass processes and store properties
  • Inject store into application

Any subsequent updates the store will be passed down to the widget. There is even more you could do with your store. I could have wrapped my widget in a StoreProvider and passed store properties down, and manually subscribe to store changes. Subscribing to store changes could be a very useful tool inside your applications for some granular control of managing widget visibility for example.

Processes even provide the ability to add middleware so you can add logging and authentication, or maybe payload validation without having to pollute your processes with extra code and possibly extra sources of errors.

At the end of the day, I have had a fun time learning how to use processes and stores in my applications. They provide a lot of flexibility in managing state and I can already see how useful they would be in building out an application as it grows!

Be sure to subscribe to the newsletter and stay up to date with the latest content!

Style Dojo Widgets

dojo styling

There’s an entire Dojo tutorial on creating custom themes, and if you look at any of the guides on creating widgets you will learn how to create css modules to use with your widgets and your applications.

But how about if you want to use some of the out-of-the-box widgets with Dojo? There is an entire library of ready to use widgets you can start building off for your applications today. We saw this when we created a datepicker, and there’s even widgets like select and button. Check out the widget showcase for more!

If you just wanted to quickly get started with a nice looking theme, you can use the Dojo theme. I’ll cover how to create your own themes another time, but for now, let’s assume you have a Dojo widget in your application and you really like the Dojo theme, but you would like to slightly make some adjustments. Not enough to warrant a custom theme, but maybe add some italic font, change a background here and there.

Dojo provides a way to do this in a CSS modules friendly manner. Every widget has a section in its documentation on the CSS classes you can override, such as those in the select widget. To get started, let’s add the Dojo theme to this widget.

import theme from "@dojo/themes/dojo";
...

// in your render method
<Select
  theme={theme} // apply the Dojo theme
  options={names}
  value={this.selectedValue}
  placeholder="Pick a name"
  onChange={this.onSelectChange}
/>

At this point, you’re able use a nice looking theme provided by Dojo. However, maybe I would like the placeholder text to be italicized, and I’d like to change the background color of the button and some other colors a bit. I don’t want to change much, just a few things.

.arrow {
  background: #959595;
  color: #fff;
}

.focused {
  color: #005e95;
}

.placeholder {
  font-style: italic;
}

.inputWrapper {
  color: #6e6e6e;
}

Now I have some simple CSS that matches the class names in the documentation. You can now apply these class names to your Select widget using a plain old JavaScript object that will map the extra classes to the widget key. You can see more detail here.

const SelectClasses = {
  "@dojo/widgets/select": {
    arrow: [css.arrow],
    focused: [css.focused],
    placeholder: [css.placeholder],
    inputWrapper: [css.inputWrapper]
  }
};

// in your render method
<Select
  theme={theme} // apply the Dojo theme
  classes={SelectClasses}
  options={names}
  value={this.selectedValue}
  placeholder="Pick a name"
  onChange={this.onSelectChange}
/>

This will now maintain the Dojo theme you are already using, but also apply the classes you are adding the widget as well. You end up with an application that looks something like below.

As you can see, it doesn’t take a lot of work to use the provided Dojo theme with your applications and apply your own little CSS sugar to the out-of-the-box Dojo widgets. You can also take this a step further and create your own custom theme for your application and even a reusable theme for a suite of applications! Happy dev’ing!

Build Time Rendering in Dojo

Dojo Build Time Rendering

You may have worked with other frameworks that support server side rendering. What it basically does is render the HTML of your page and pass it down to the client as it would look when the initial JavaScript loads and then you can interact with it and the JavaScript stuff works its magic to make a cool interactive application.

The Dojo method of doing this is much simpler. Instead of rendering the pages on the server, you can create your pages during the build process, and then you can just upload it anywhere.

You can find some information about Build Time Rendering on the github page for dojo/cli-build-app. You can get started by using the dojo/cli to quickly scaffold an application and modify it a bit.

Once you have your template application ready to go, let’s make some modifications. First thing we need to do in src/index.html is add a root div that the build time rendering tools can work with.

<!DOCTYPE html>
<html lang="en-us">
<head>
  <title>dojo-btr</title>
  <meta name="theme-color" content="#222127">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <div id="root"></div> <!-- Add this element -->
</body>
</html>

Now we can set up the configuration for build time rendering. One thing to note is that by default, the template application uses hash routing, meaning that routes look like myapp/#about. This will generate a single index.html file in your build that will quickly load those routes. If you use a different history manager, it will create an index.html for each route.

{
  "build-app": {
    "build-time-render": {
      "root": "root",
      "paths": [
        "#home",
        "#about",
        "#profile"
      ]
  }
}

Note that I have prefixed my paths with a # so that the BTR can generate the pages correctly. The output of this is pretty interesting. Each route is stored in an array as strings, and as you change your route at runtime, it will load the HTML of that route as needed.

The benefit here is that your HTML is ready to go and the JavaScript parts just do their thing without having to do an initial render of your page. It makes for a very responsive experience. You get a lot of benefit from Build Time Rendering with some simple configuration, so take advantage of it!

You can see a sample of how this looks in a sample application I put together here. I also have it running live here.