Testing Text Truncation: Effective Strategies
Tags: react, testing, html, jest
Published: 2023-09-03 22:14:12

I think many developers have encountered issues with broken UI layouts caused by unexpectedly long text elements on the frontend. 
When text content exceeds the available space, truncation techniques like CSS's `text-overflow` or JavaScript functions need to be applied. 

Ensuring that these truncations work flawlessly across various scenarios requires effective testing strategies. In this article, we will delve into approaches for testing UI components that involve truncation, whether achieved through CSS (`text-overflow`) or JavaScript functions.

## Truncation Implemented with JavaScript

When utilizing JavaScript for truncating text, either through libraries like the popular [lodash](https://lodash.com/docs/4.17.15#truncate) `_.truncate` function or custom implementations, testing becomes relatively straightforward. We can write unit tests to cover the different scenarios we expect to encounter.

For instance, when using a custom truncation function, a simple test could involve verifying the return value:

```javascript
import { truncate } from './custom-truncation.js';

it("truncates the text") {
  expect(truncate("your long text", 10)).toEqual("expected")
}
```

In scenarios involving libraries, it might be worthwhile to test the truncation within the component itself. For instance, in a React component:

```react
import { render, screen } from '@testing-library/react'

it('should render menu with truncated header', () => {
  render(<Menu {...props} />)

  const header = screen.getByRole('header')
  expect(title).toHaveTextContent('This is some looong title too long...')
})
```

Component-level testing provides a more direct insight into how the truncation behaves in the context of your UI.

## Handling Truncation through CSS

However, things become more intricate when dealing with truncation accomplished via CSS properties like `text-overflow: ellipsis`. The initial instinct might be to write tests similar to those above—render the component and inspect the text content. But, unfortunately, this approach will not yield accurate results.

Due to the nature of `text-overflow` truncation, the entire text remains within the HTML element, causing assertions to fail.

![Image with truncated HTML element using CSS and JS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3gb1orzcrryscl1kfj7.png)

Link to the [pen](https://codepen.io/davefres/pen/MWZjYOz).

So, how do we effectively test CSS-based truncation?

To confirm successful truncation via `text-overflow`, we can implement a simple validation technique by comparing `offsetWidth` and `scrollWidth`:

```javascript
function isEllipsisActive(e: HTMLElement) {
  return (e.offsetWidth < e.scrollWidth);
}
```

(For a quick refresher on `offsetWidth` and `scrollWidth`, refer to this [explanation](https://stackoverflow.com/a/21064102)).

However, there's a catch. If your testing library relies on jsdom, accessing `offsetWidth` and `scrollWidth` directly from an element might not work due to rendering limitations. This limitation is further elaborated upon [here](https://github.com/testing-library/react-testing-library/issues/353#issuecomment-618364310).

Nonetheless, if you're using a library like Cypress, which performs actual rendering, you can incorporate this validation into your test:

```javascript
it("should render menu with truncated header", () => {
  cy.visit("your_page_url"); // Replace with the URL of the page containing the element

  cy.get('[data-testid="your_element_id"]').then(($element) => {
    const offsetWidth = $element[0].offsetWidth;
    const scrollWidth = $element[0].scrollWidth;

    expect(offsetWidth).to.be.lessThan(scrollWidth);
  });
});
```

But what if your only option is a library that relies on jsdom, and accessing params such as `offsetWidth` is not feasible? 

In that case, an alternative approach involves validating the existence of specific styles applied to the HTML element:

```javascript
it('should render menu with truncated header', () => {
  // selection of element
  // ...

  expect(element).toHaveStyle('text-overflow: ellipsis');
  expect(element).toHaveStyle('overflow: hidden');
  expect(element).toHaveStyle('white-space: nowrap');
});
```

While not as robust as testing the actual rendered value, this method can still serve as a viable solution in certain scenarios.


## Results

- **JavaScript Truncation:** 
  - Write unit tests covering your truncation function
  - Write component tests checking the actual text inside DOM after truncation
- **CSS Truncation:**
  - Verify truncation using rendering libraries like Cypress, comparing `offsetWidth` and `scrollWidth`
  - For jsdom-based libraries ensure expected CSS properties are present

By following these steps, we can ensure consistent and reliable truncation behavior.