How to Unit Test CSS in a Custom Block in Element

Element makes unit testing of custom Blocks easy using Jest and Enzyme. This article discusses testing custom props that dynamically set CSS styles in a custom Block.

Introduction

Unit testing is a fundamental principle of software development that improves reliability and reduces maintenance.

For an introduction to unit testing Element, please see the previous article How to Unit Test a Custom Block in Element. That article explains why you should write unit tests and includes a demonstration of writing basic unit tests in Element.

This article expands on unit testing to examine testing CSS styles, specifically how it is possible to test user-editable CSS styles when writing a custom Block using Element.

This tutorial continues to build on the previous article showing how to build a Custom Font Size Heading Block.

Please refer back to that article, How to Make Font Size Editable in an Element Block, for the code to create that custom Block.

You will be adding unit tests to test the Block, instead of skipping them as in that tutorial.

To follow along exactly, please follow the previous tutorial up until the heading “Update your test files to skip the tests.”

This tutorial will provide the test files to test the dynamic CSS functionality of that custom Block.

Unit Testing the CSS With the Default Content

In a text editor or IDE (such as VS Code ), start by opening the /__tests__/Block.spec.js file.

First, add an additional import to the top of the file:

import { flushToStyleTag } from "aphrodite"

This will be necessary to flush Aphrodite’s buffer, as described in the next section.

Then, find the statement describe("when there is no custom data", () => { ... }) in the file, and replace that entire statement (called a “test suite”) with the following code:

describe("when there is no custom data", () => {
it("should render the block with the default font sizes", () => {
const wrapper = mount(<Block {...props} />)
// Force Aphrodite to output the CSS-in-JS styles in time to test them:
flushToStyleTag()
const h1Element = wrapper.find("h1").at(0)
const pElement = wrapper.find("p").at(0)

// Make sure our CSS-in-JS decorator successfully injected the CSS class
expect(h1Element.hasClass(/heading_(\w+)/)).toBe(true)
expect(pElement.hasClass(/text_(\w+)/)).toBe(true)

//Expect the computed CSS style to match the default heading font sizes
expect(getComputedStyle(h1Element.getDOMNode()).getPropertyValue("font-size"))
.toBe(defaultConfig.headingFontSizeString)
expect(getComputedStyle(pElement.getDOMNode()).getPropertyValue("font-size"))
.toBe(defaultConfig.textFontSizeNumber + defaultConfig.cssLengthUnit)
})
})

The unit test starts by mounting the Block using Enzyme’s .mount() function, as you have seen in the previous article. 

From there, the unit test makes sure that both the heading and text have the correct CSS based on the defaultConfig (retrieved from /src/configs.js).

The next section discusses each specific step necessary for testing the CSS.

Examining Aphrodite’s flushToStyleTag() Function

Element uses the npm package (JavaScript library) called Aphrodite for creating CSS styles using CSS-in-JS (also known as JSS).

For an overview of using Aphrodite in Element, please refer to How To Style A Block With Aphrodite and Styling Your Block With CSS in the Element Platform Documentation .

When using Aphrodite, it automatically tries to buffer writes to the <style> tag that it generates in order to minimize the number of DOM modifications that happen.

This buffering helps to speed up the injection of styles into your custom Block in a VOLT store, but it can break your ability to write a unit test for the CSS.

Aphrodite uses asap to schedule buffer flushing. If you measure DOM elements' dimensions in componentDidMount or componentDidUpdate, you can use setTimeout or flushToStyleTag to ensure all styles are injected. —Aphrodite Docs on GitHub 

Adding a call to Aphrodite’s flushToStyleTag() makes sure the styles are available for you to test.

Selecting a DOM element with Enzyme’s .find()

To find the elements whose styles you want to test, call Enzyme’s .find() method on the ReactWrapper object you previously mounted (i.e. the custom Block):

const h1Element = wrapper.find("h1").at(0)
const pElement = wrapper.find("p").at(0)

This method returns a new ReactWrapper containing just the selected element, <h1> or <p>.

The .at() method is technically optional here, as the Custom Font Size Heading Block has only one instance of each <h1> and <p> tag.

If there were additional <h1> heading or <p> paragraph elements, then .at(0) would be necessary to select the first <h1> or <p> element respectively.

Using Enzyme’s .hasClass() with a Regular Expression

Aphrodite creates custom CSS classes that are “injected” (added) dynamically to the elements that it styles. These classes have random class names, generated by combining the desired class name (from /src/getStyles.js in the custom Block) with an underscore and a random series of alphanumeric characters. Using a dynamic class name prevents CSS naming collisions , where multiple CSS classes have the same name.

For example, the “heading” class (from /src/getStyles.js) might actually be called “heading_d52pcl” in the output when the Block is rendered (displayed).

The Enzyme function .hasClass(className) allows you to use a regular expression (a JavaScript RegExp ) to look for the dynamic class name.

expect(h1Element.hasClass(/heading_(\w+)/)).toBe(true)
expect(pElement.hasClass(/text_(\w+)/)).toBe(true)

This command is wrapped in the Jest function expect(), which is used with .toBe() to assert (make sure) that the values are true. When these tests pass, it means that both the <h1> and <p> elements each have the correct custom class injected by Aphrodite.

Fetching a DOM Element with Enzyme’s .getDOMNode()

The Enzyme method .getDOMNode() turns the ReactWrapper object into its corresponding DOM node .

h1Element.getDOMNode()
pElement.getDOMNode()

The DOM node, also called the DOM element, is basically the HTML element specified in the Document Object Model (DOM) .

In this case, it refers to the <h1>...</h1> and <p>...</p> tags selected with the .find() method.

This method is necessary for inspecting the actual CSS applied to the DOM node.

Combining getComputedStyle and .getPropertyValue

The built-in JavaScript method getComputedStyle(element) returns the computed (or calculated) CSS styles for the DOM element provided to it.

These styles are then accessible using the .getPropertyValue(property) method:

getComputedStyle(h1Element.getDOMNode()).getPropertyValue("font-size"))
getComputedStyle(pElement.getDOMNode()).getPropertyValue("font-size"))

The getComputedStyle() method is part of the global Window object and could also be called by specifying window.getComputedStyle().

Note that the property values are accessed using hyphenated CSS syntax (font-size), not camelCased CSS-in-JS syntax (fontSize).

Writing a Unit Test for a Computed CSS Style

Combining the various Enzyme and built-in methods, you can write a unit test comparing the computed CSS styles to the expected CSS styles using Jest’s expect() function:

// Expect the computed CSS style to match the default heading font sizes
expect(getComputedStyle(h1Element.getDOMNode())
.getPropertyValue("font-size"))
.toBe(defaultConfig.headingFontSizeString)
expect(getComputedStyle(pElement.getDOMNode())
.getPropertyValue("font-size"))
.toBe(defaultConfig.textFontSizeNumber + defaultConfig.cssLengthUnit)})

These tests assert that the computed CSS values are equal to the expected CSS property values. If they are equal, the tests will pass. If they are not equal, the tests will fail. The expected CSS properties are retrieved from the /src/configs.js file.

Next you will write a unit test for the CSS generated when custom content is passed as props (React properties) to the Block.

Unit Testing the CSS with Custom Data

Having familiarized yourself with the various function calls, it is easy to write a test to see how the CSS styles change when custom props are provided to the Block.

In your text editor or IDE, open the /__tests__/Block.spec.js file if it is not already open.

Find the statement describe("when given custom data", () => { ... }) in the file, then replace the entire statement with the following code:

describe("when given custom data", () => {
it("should render the block with the custom font sizes", () => {
const customHeadingFontSizeString = "4em"
const customTextFontSizeNumber = 30
const customCssLengthUnit = "px"
const wrapper = mount(
<Block
{...props}
headingFontSizeString={customHeadingFontSizeString}
textFontSizeNumber={customTextFontSizeNumber}
cssLengthUnit={customCssLengthUnit}
/>
)
// Force Aphrodite to output the CSS-in-JS styles in time to test them:
flushToStyleTag()
const h1Element = wrapper.find("h1").at(0)
const pElement = wrapper.find("p").at(0)

// Make sure our CSS-in-JS decorator successfully injected the CSS class
expect(h1Element.hasClass(/heading_(\w+)/)).toBe(true)
expect(pElement.hasClass(/text_(\w+)/)).toBe(true)

// Expect the computed CSS style to match the default heading font sizes
expect(getComputedStyle(h1Element.getDOMNode()).getPropertyValue("font-size"))
.toBe(customHeadingFontSizeString)
expect(getComputedStyle(pElement.getDOMNode()).getPropertyValue("font-size"))
.toBe(customTextFontSizeNumber + customCssLengthUnit)})
})
})

This test file is similar to the default unit tests, except it includes custom data provided directly in the test. These custom elements are passed to the Block as props when it is mounted by Enzyme.

Then, the assertions (the expect statements) were changed to expect the custom data from the test.

Writing a unit test with custom data replaces manual testing that would otherwise be performed by modifying the local testing data in the /local/index.js file (specifically, in the const localEnvPropOverrides variable) and then examining the changes in a local browser (started using npm run start).

Writing the AMP Unit Tests

The test file for the Google AMP (Accelerated Mobile Pages) version of the Block will be almost identical, except one line.

First, copy the entire code from the /__tests__/Block.spec.js file.

Next, replace the entire contents of the /__tests__/AMP.spec.js file by pasting the copied code.

Then, change the statement “utils” (in the beforeEach code block) to read:

utils: { ...utils, isAmpRequest: true },

This is the only change needed to create the unit tests for the AMP version of your custom Block.

Running the test suite

Try running the tests using npm run test, and you should see all 4 tests pass:

 PASS __test__/Block.spec.js (4.951s)
PASS __test__/AMP.spec.js (4.932s)
Test Suites: 2 passed, 2 total
Tests: 4 passed, 4 total

If you already had the test runner running in watch mode, then you would have seen the tests change from failing to passing as you modify the files.

Having tested the CSS, you could consider adding the basic unit tests from the last article to test the behavior of the text properties, as follows.

In the second test suite — describe("when there is no custom data", () => { … } — add the test:
it("should render the block with the default content", () => {
const wrapper = mount(<Block {...props} />)
expect(wrapper.text()).toBe(defaultConfig.heading + defaultConfig.text)
})

In the third test suite — describe("when given custom data", () => { … } — add the test:

it("should render the block using the custom data", () => {
const customHeading = "Custom Block Heading"
const customText = "Custom Block Text"
const wrapper = mount(<Block {...props} heading={customHeading} text={customText} />)
expect(wrapper.text())
.toBe(customHeading + customText)
})

Now your Custom Font Size Block has unit tests for its CSS functionality as well as its basic text functionality.

Conclusion

Since having passing tests is a requirement for publishing custom Blocks to VOLT’s Site Designer, it is useful to be able to test all aspects of the custom Blocks, including CSS. Using its included testing tools (Jest and Enzyme), Element allows you to unit test dynamic CSS properties, as shown in this article.

Custom CSS classes and properties in custom Blocks are created by the included Aphrodite package, which injects the styles into the custom Block. Even though Aphrodite’s generated CSS classes have random characters, the injection can be tested using Enzyme’s .hasClass(className) function with a regular expression. Since Aphrodite uses a buffering system to improve performance, unit tests for computed CSS properties need to call Aphrodite’s flushToStyleTag() function.

The CSS unit tests shown in this article would allow you to update the functionality Custom Font Size Heading Block while making sure that the changes did not cause problems. The same style of CSS unit tests could be used to test whether chosen colors were applied correctly to your custom Block, such as for font colors and background colors.

For information on adding a color picker to your custom Block, please read the article How To Style A Block With Aphrodite in the Element Platform Documentation.