sineverba
sineverba

Reputation: 5172

How can I simulate a click on Highchart

This is my DashboardPage component:

export const DashboardPage = (props) => {

    const [mounted, setMounted] = useState(false);

    const { index } = props;

    let navigate = useNavigate();

    useEffect(() => {
        if (mounted === false) {
            setMounted(true);
            index();
        }
    }, [mounted, index]);

    const optionNUsersPerCompany = {
        chart: {
            plotBackgroundColor: null,
            plotBorderWidth: null,
            plotShadow: false,
            type: 'pie'
        },
        title: {
            text: 'Utenti per azienda'
        },
        tooltip: {
            pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
        },
        accessibility: {
            point: {
                valueSuffix: '%'
            }
        },
        plotOptions: {
            pie: {
                allowPointSelect: true,
                cursor: 'pointer',
                dataLabels: {
                    enabled: true,
                    format: '<b>{point.name}</b>: {point.percentage:.1f} %'
                },
            }
        },
        series: [{
            name: 'Utenti',
            colorByPoint: true,
            data: getDataUsersPerCompany(props.items),
            point: {
                events: {
                    click: (e) => {
                        console.log(e);
                        props.setCompanyIdInUsersStore(e.target.point.idCompany);
                        return navigate(PATH_USERS);
                    }
                }
            }
        }]
    }

    return (
        <Chart options={optionNUsersPerCompany} />
    )
}

const mapDispatchToProps = (dispatch) => {

    return {
        index: () => dispatch(statsActions.index()),
        setCompanyIdInUsersStore: (id) => dispatch(companiesActions.setCompanyIdInUsersStore(id)),
    };
}

const mapStateToProps = (state) => {

    return {
        items: state.stats.items,
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);

And this is the simple Highchart Component:

import React, { useState } from "react";

import HighchartsReact from "highcharts-react-official";
import Highcharts from 'highcharts';

export default React.memo(function Chart(props) {

    const [currentOptions, setCurrentOptions] = useState({});

    /**
     * Questo controllo serve a non re-draware per due volte di seguito i containers
     */
    if (JSON.stringify(props.options) !== JSON.stringify(currentOptions)) {
        setCurrentOptions(props.options);
    }

    return (
        <HighchartsReact
            highcharts={Highcharts}
            options={currentOptions}
            immutable={true}
        />
    );
});

I want to test the click on plotOptions with Jest in React Highcharts. This is the resulting HTML page from Jest:

  <div>
    <div
      data-highcharts-chart="1"
      style="overflow: hidden;"
    >
      <div
        class="highcharts-container "
        dir="ltr"
        id="highcharts-r3a94y8-149"
        style="position: relative; overflow: hidden; width: 600px; height: 400px; text-align: left; line-height: normal; z-index: 0; user-select: none; outline: none;"
      >
        <svg
          class="highcharts-root"
          height="400"
          style="font-family: \"Lucida Grande\", \"Lucida Sans Unicode\", Arial, Helvetica, sans-serif; font-size: 12px;"
          version="1.1"
          viewBox="0 0 600 400"
          width="600"
          xmlns="http://www.w3.org/2000/svg"
        >
          <desc>
            Created with Highcharts 10.1.0
          </desc>
          <defs>
            <clippath
              id="highcharts-r3a94y8-151-"
            >
              <rect
                fill="none"
                height="375"
                width="580"
                x="0"
                y="0"
              />
            </clippath>
          </defs>
          <rect
            class="highcharts-background"
            fill="#ffffff"
            height="400"
            rx="0"
            ry="0"
            width="600"
            x="0"
            y="0"
          />
          <rect
            class="highcharts-plot-background"
            fill="none"
            height="375"
            width="580"
            x="10"
            y="10"
          />
          <rect
            class="highcharts-plot-border"
            data-z-index="1"
            fill="none"
            height="375"
            width="580"
            x="10"
            y="10"
          />
          <g
            class="highcharts-series-group"
            data-z-index="3"
          >
            <g
              class="highcharts-series highcharts-series-0 highcharts-pie-series highcharts-tracker"
              data-z-index="0.1"
              opacity="1"
              style="cursor: pointer;"
              transform="translate(10,10) scale(1 1)"
            >
              <path
                class="highcharts-point highcharts-color-0"
                d="M 290 175 A 0 0 0 0 1 290 175 L 290 175 A 0 0 0 0 0 290 175 Z"
                fill="#7cb5ec"
                opacity="1"
                stroke="#ffffff"
                stroke-linejoin="round"
                stroke-width="1"
                transform="translate(0,0)"
              />
            </g>
            <g
              class="highcharts-markers highcharts-series-0 highcharts-pie-series"
              data-z-index="0.1"
              opacity="1"
              transform="translate(10,10) scale(1 1)"
            />
          </g>
          <text
            class="highcharts-title"
            data-z-index="4"
            style="color: rgb(51, 51, 51); font-size: 18px; fill: #333333;"
            text-anchor="middle"
            x="300"
            y="24"
          >
            Utenti per azienda
          </text>
          <text
            class="highcharts-subtitle"
            data-z-index="4"
            style="color: rgb(102, 102, 102); fill: #666666;"
            text-anchor="middle"
            x="300"
            y="10"
          />
          <text
            class="highcharts-caption"
            data-z-index="4"
            style="color: rgb(102, 102, 102); fill: #666666;"
            text-anchor="start"
            x="10"
            y="397"
          />
          <g
            class="highcharts-data-labels highcharts-series-0 highcharts-pie-series highcharts-tracker"
            data-z-index="6"
            opacity="0"
            style="cursor: pointer;"
            transform="translate(10,10) scale(1 1)"
          >
            <path
              class="highcharts-data-label-connector highcharts-color-0"
              d="M 295 370 C 290 370 290 352 290 346 L 290 340"
              fill="none"
              stroke="#7cb5ec"
              stroke-width="1"
            />
            <g
              class="highcharts-label highcharts-data-label highcharts-data-label-color-0"
              data-z-index="1"
              style="cursor: pointer;"
              transform="translate(300,360)"
            >
              <text
                data-z-index="1"
                style="color: rgb(0, 0, 0); font-size: 11px; font-weight: bold; fill: #000000;"
                x="5"
                y="16"
              >
                <tspan
                  style="font-weight: bold;"
                >
                  acme
                </tspan>
                : 100.0 %
              </text>
            </g>
          </g>
          <g
            class="highcharts-legend highcharts-no-tooltip"
            data-z-index="7"
          >
            <rect
              class="highcharts-legend-box"
              fill="none"
              height="8"
              rx="0"
              ry="0"
              visibility="hidden"
              width="8"
              x="0"
              y="0"
            />
            <g
              data-z-index="1"
            >
              <g />
            </g>
          </g>
          <text
            class="highcharts-credits"
            data-z-index="8"
            style="cursor: pointer; color: rgb(153, 153, 153); font-size: 9px; fill: #999999;"
            text-anchor="end"
            x="590"
            y="395"
          >
            Highcharts.com
          </text>
        </svg>
      </div>
    </div>
  </div>

Upvotes: 2

Views: 1671

Answers (1)

Parth Tomar
Parth Tomar

Reputation: 261

The coverage statistic is correct, as Highcharts render the elements using SVG and other components (path, rect etc.) that do not support click events simulation by DOM. If you replace click with mouseOver, your test coverage would hit 100%, as it is supported. Highcharts team might be doing some custom implementation to achieve click handlers and we need to utilize that to solve your use case. @ppotaczek pointed out the correct approach.

Code modifications:

  • Assume DashboardPage component accepts an optional prop, onAfterChartCreated.
  • It passes that prop over to Chart component as value of callback.

import { useState, useEffect, useRef } from "react";
import {useNavigate} from 'react-router-dom';
import Chart from "./Chart";
import {connect} from 'react-redux';

export const DashboardPage = (props) => {

    // previous code
    return (
        <Chart options={optionNUsersPerCompany} callback={props.onAfterChartCreated} />
    )
}

// follow up code

export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);

  • Chart component should be modified to:

import React, { useState } from "react";

import HighchartsReact from "highcharts-react-official";
import Highcharts from 'highcharts';

export default React.memo(function Chart(props) {

    const [currentOptions, setCurrentOptions] = useState({});

    /**
     * Questo controllo serve a non re-draware per due volte di seguito i containers
     */
    if (JSON.stringify(props.options) !== JSON.stringify(currentOptions)) {
        setCurrentOptions(props.options);
    }

    return (
        <HighchartsReact
            highcharts={Highcharts}
            options={currentOptions}
            immutable={true}
            callback={props.callback}
        />
    );
});

callback is a prop on HighCharts react wrapper, that executes when chart is rendered and returns an instance of the chart. You could refer to their docs for more info or check out How do i access highcharts api after component renders?

Now we modify the test case for DashboardPage component as follows:

const afterChartCreatedCallback = (chart) => {
  // We can now trigger click on any data point using Highcharts API
  chart.series[0].data[0].firePointEvent('click');
}

render(<DashboardPage onAfterChartCreated={afterChartCreatedCallback} />);

So, we only use the callback in the test, to improve the coverage. This should increase your DashboardPage component coverage to 100%.

Upvotes: 4

Related Questions