documentationfor yFiles for HTML 2.6

Testing

This chapter describes the specifics to be aware of when testing yFiles apps with popular testing frameworks.

Testing Environment

When testing your yFiles for HTML application, please note that the view part of the library requires a complete DOM implementation. In particular, a working SVG DOM is required.

Popular Javascript implementations of browser-like environments like jsdom work well for testing basic web application functionality. However, they do not provide a suitable environment for testing yFiles visualizations. Trying to run yFiles for HTML applications in such environments can lead to obscure errors.

Therefore, when testing the yFiles view part of your app, make sure to use a testing framework that provides a real browser environment like e.g. Puppeteer, Playwright, or Karma.

The parts of yFiles for HTML which do not use visual features can work in such restricted environments, though. These are

  • DefaultGraph as long as the elements are created using void styles.
  • The layout and algorithms API without LayoutExecutor, i.e. without the view-layout-bridge module.

End to End (E2E) Testing

While it is possible to conduct automated UI tests with yFiles for HTML, there is no out-of-the-box support for identifying individual graph items by their corresponding visualization in the SVG DOM or Canvas. However, there are ways to establish a relationship between nodes and their visualizations to ensure better testing accuracy, as outlined in Getting Nodes from SVG Elements and vice versa.

Additionally, to test simulated user interactions, sequences of events such as mousemove, mousedown, and mouseup must be provided. See Simulating User Interaction for more details.

We suggest implementing the following alternatives or a combination of them:

  • Test the graph structure itself, i.e. the GraphComponent's graph, rather than the DOM.
  • Test the visualization using a tool that compares image snapshots.

Getting Nodes from SVG Elements and vice versa

At a technical level, there is only a one-way relationship between the model and the resulting elements in the DOM. This is due to performance considerations and to prevent difficult-to-debug issues and memory leaks. As a result, there is no direct connection from a DOM element back to the model. Typically, such a relationship is unnecessary, except possibly in testing scenarios.

In the event that such a connection is required, it can be created by iterating over the model and obtaining the corresponding SVG element using the GraphModelManager:

/**
 * @param {!IModelItem} item
 * @returns {?SVGElement}
 */
function getSvgElementForItem(item) {
  const canvasObject = graphComponent.graphModelManager.getCanvasObject(item)
  if (canvasObject) {
    const visual = graphComponent.getVisual(canvasObject)
    if (visual instanceof SvgVisual) {
      return visual.svgElement
    }
  }
  return null
}function getSvgElementForItem(item: IModelItem): SVGElement | null {
  const canvasObject = graphComponent.graphModelManager.getCanvasObject(item)
  if (canvasObject) {
    const visual = graphComponent.getVisual(canvasObject)
    if (visual instanceof SvgVisual) {
      return visual.svgElement
    }
  }
  return null
}

The retrieved SVG element can be used in two ways: to attach DOM attributes to the SVG, making it more easily recognizable in DOM testing tools, or to build a map from DOM elements to graph elements.

It is also possible to find a model item for a given SVG element by using the element’s location, such as its center:

/**
 * @param {!Point} location
 * @returns {?INode}
 */
function getNodeAtLocation(location) {
  const hitTest = graphComponent.graphModelManager.createHitTester(INode.$class)
  const hits = hitTest.enumerateHits(graphComponent.inputModeContext, location)
  return hits.find()
}function getNodeAtLocation(location: Point): INode | null {
  const hitTest = graphComponent.graphModelManager.createHitTester(INode.$class)
  const hits = hitTest.enumerateHits(graphComponent.inputModeContext, location)
  return hits.find()
}

Simulating User Interaction

yFiles for HTML utilizes input modes to manage user interaction. These input modes rely on sequences of events such as mousemove, mousedown, and mouseup to occur in a specific order. However, some testing frameworks may not accurately replicate these sequences, leading to improperly simulated input gestures. If this occurs, it is necessary to replicate the exact sequence of events that occurs in real-world scenarios.

For instance, pressing the mouse at a designated location often necessitates moving the mouse to that location first. Similarly, a mouse click with a modifier key held down is usually preceded by a keydown event for that key.

A very common requirement for input modes is that the mouse cursor must hover over an item before the input gesture can begin. E.g. to move a node, the minimum sequence of events would involve:

  • mousemove to a location over the node
  • mousedown at that location
  • mousemove to the target location
  • mouseup at the target location

Testing Frameworks

Keeping the aforementioned restrictions in mind, any JavaScript testing framework can be used to test a yFiles for HTML application. This section lists a choice of the most common testing frameworks. This list is not meant to be exhaustive, frameworks that are not listed here can be used as well.

Cypress
The Cypress demo shows how to set up an E2E test for a yFiles for HTML application. It also demonstrates Cypress’s built-in video recording.
Playwright
The Playwright demo runs its test in Chrome, Firefox, and Webkit in headless mode.
Selenium-Webdriver
The Selenium WebDriver demo runs tests in Chrome and Firefox in headless mode. It uses the Mocha test runner and the Chai assertion framework.

Vitest
As explained above, jsdom does not provide a full DOM implementation and therefore cannot be used for testing yFiles visualizations. The same is true for the alternative happy-dom environment supported by Vitest. Hence, the Vitest node (default), jsdom, and happy-dom environments can only be used for basic unit tests that do not involve yFiles visualizations.Vitest’s Playwright example shows using a full DOM environment.The Vitest demo shows how to use Playwright together with Vitest.
WebdriverIO
The Webdriver IO demo shows cross browser testing in headless mode.
Web Test Runner
Web Test Runner is a speedy and efficient tool that allows you to run tests directly in a browser (in headless mode if necessary). It provides support for the common browser launchers like Puppeteer, Playwright, Selenium, or WebdriverIO, and comes with Mocha as its default testing framework.It also can be used with Vite using the web-test-runner-vite-plugin.
Jest
See the following section for more information on testing with Jest.

Jest

Jest is a testing framework that runs tests on Node.js. It is possible to test components that use yFiles for HTML with Jest. However, some scenarios require extra work.

The Jest demo shows how to test yFiles for HTML functionality without a DOM. Unlike the other demos, it shows testing DefaultGraph itself as well as testing with a mocked DefaultGraph.

The Jest Puppeteer demo runs Jest tests in a puppeteer environment to provide a fully HTML 5 compliant browser environment.

ES Modules

Jest’s support for native es-modules is currently still experimental, which is why JavaScript (or TypeScript) code with es modules is usually transpiled. If the yFiles application under test uses es-modules, the yFiles library would need to be transpiled, as well. Due to its size, this is rather slow. As a workaround, it is possible to map the yFiles es-modules variant to the UMD variant, which does not need to be transpiled. You can do so with the moduleNameMapper configuration property:

module.exports = {
  moduleNameMapper: {
    // map the es-modules yFiles variant to the UMD variant, so it doesn't need to be transpiled
    '^yfiles$': '<rootDir>/node_modules/yfiles-umd'
  }
  // ...
}

Mocking

Some parts of yFiles (such as GraphComponent or item styles) require a complete DOM implementation, which Jest’s JSDOM environment does not provide. For this reason, it is necessary to mock yFiles for the respective tests. Unfortunately, Jest’s automatic mocking mechanism doesn’t work well with yFiles for HTML because of yFiles' internal structure.

Sometimes it is possible to replace item styles in the test environment with their respective Void- implementations (e.g. VoidLabelStyle) to be able to test yFiles components without the need for a complete mock.

yFiles can be manually mocked by either placing a __mocks__/yfiles.js next to the node_modules folder or by calling jest.mock('yfiles', …​) toplevel in a test file. The former will mock yFiles in all test files, while the latter will mock yFiles only in the respective test files.

An example for a __mocks__/yfiles.js mock file would be

export class GraphComponent {
  get graph() { /* ... */ }
}

An example for calling jest.mock would be

import { GraphComponent } from 'yfiles'

jest.mock('yfiles', () => ({
  GraphComponent: class GraphComponent {
    get graph() { /* ... */ }
  }
}))