This blog post covers test driven development (TDD) for SAPUI5 Controls by means of QUnit. It focuses on testing of animated UI control with interaction build with third party library.

For more details concerning TDD please see following blog post engineering-in-javascript.

What is a SAP UI5 Control?

A SAPUI5 Control is “… an object that defines the appearance and behavior of a screen area” (wiki). In general SAPUI5 provides a lot of controls for building an application.

Why should I create one?

You can create your own control in case of standard controls cannot meet your requirements. You can extend an existing standard control or you create your own. This blog post focuses on creating a new control.

What is our example?

  • Donut chart (Pie chart with hole in the middle)
    • Contains a number (e.g.  5 Customers)
  • Number section in the middle of the donut chart
    • Contains either total number of all wedges or number for selected wedge
  • Interaction
    • User can click on wedge
    • User can move mouse over wedge
    • User can move mouse out
  • Events
    • Select event triggered when user clicks or moves the mouse over one wedge
    • Deselect event triggered when user moves the mouse out
  • Animation
    • Wedges are drawed with animation during
      initialization or update
  • Third party D3.js (Data-Driven-Documents) is used for drawing the donut chart

Donut_Chart.png

What is the challenge?

Animation

The main challenge in our example is animation. Each time our donut chart is drawed an animation is triggered that takes about one second until the chart is rendered completely. This happens during startup but also when an update is triggered. What if we need to check the total number of wedges rendered in the
Document Object Model (DOM) but the animation takes some time to come to an end? One short-term solution is to start the test until everything is rendered
(Listing 1). In the following code snippet our donut chart is initialized outside the QUnit module.

Listing 1 (Delayed test unit):


var oDonutChart = new sap.hpa.grcaud.chart.DonutChart({
 id: "donut"
});
oDonutChart.placeAt("content");
module("Donut Chart is initialized", {
 setup : function() {
 },
 teardown : function() {
 }
});

test("shall create one wedge", function() {
 var iNumberOfWedge = oDonutChart.getNumberOfWedge();
 equal(iNumberOfWedge, 1, "wedge number is wrong");
});









Unfortunately all furthermore tests need to work with same donut chart instance. Moreover global variables should be avoided. Therefore a better solution is to setup and teardown the donut chart in the module.

In listing 2 the instantiation and destruction of the donut chart moved to the module. In addition we need to make sure there is some timeout logic to draw the wedges (including animation) otherwise our test will fail. Without delay donut chart is not rendered completely.

Listing 2 (Timeout during module setup):

File: DonutChart.qunit.html


module("Donut Chart is initialized", {
        setup : function() {
            this.oDonutChart = new sap.hpa.grcaud.chart.DonutChart();
            this.oDonutChart.placeAt("content");
            stop();
            setTimeout(function() {
                start();
            }, 1000);
        },
        teardown : function() {
            this.oDonutChart.destroy();
            this.oDonutChart = null;
        }
});
test("shall create one wedge", function() {
  var iNumberOfWedge = this.oDonutChart.getNumberOfWedge();
  equal(iNumberOfWedge, 1, "wedge number is wrong");
});
. . .









File: DonutChart.js


getNumberOfWedge : function() {
var iWedge = jQuery(".sapHpaGrcaudFindingTileChart-part").length;
  return iWedge;
}




Unfortunately this delay is inaccurate and could break when animation duration changes. As a consequence our test needs to wait until the donut chart rendering has finished. Listing 3 enforces an immediate update of the donut chart (SAPUI5 Wiki) by means of SAPUI5 sap.ui.getCore().applyChanges() function. It will cause the donut chart to render completely without any animation.

Please see attachments for final source code of DonutChart.js and DonutChart.qunit.html.


Listing 3 (Usage of apply changes):

File: DonutChart.qunit.html


module("Donut Chart is initialized", {
  setup : function() {
  this.oDonutChart = new sap.hpa.grcaud.chart.DonutChart();
  this.oDonutChart.placeAt("content");
  sap.ui.getCore().applyChanges();
  },
  teardown : function() {
  this.oDonutChart.destroy();
  this.oDonutChart = null;
  }
});
test("shall create one wedge", function() {
  var iNumberOfWedge = this.oDonutChart.getNumberOfWedge();
  equal(iNumberOfWedge, 1, "wedge number is wrong");
});





Interaction

A secondary challenge is interaction. Our donut chart does support following interactions:

  • User can click on wedge
  • User can move mouse over wedge
  • User can move mouse out of wedge

When clicking or moving the mouse over a wedge the corresponding text (e.g. Wedge 1) and count (e.g. 1) is received via event (listing 4). Before we can test the result it is necessary to trigger the appropriate event. Due to the fact that we are using Scalable Vector Graphics (SVG) with D3.js this is done by means of a native browser event e.g. click. The corresponding SAPUI5 event trigger function sap.ui.test.qunit.triggerEvent() cannot be used because “there is no event handler attached using jQuery´s event system that corresponds to these events” (jQuery). Instead we create a new native event and dispatch it on the corresponding wedge. This also applies to move mouse over wedge and move mouse out of wedge tests.

Listing 4 (Interaction):


module("Donut Chart is created with five wedges", {
  setup : function() {
                  this.sWedge1Text = "";
  this.sWedge1Count = "";
  . . .
  this.oDonutChart = new sap.hpa.grcaud.chart.DonutChart({
                          wedges : [new sap.hpa.grcaud.chart.DonutChartWedge({
  text : "Wedge 1",
  count : 1
  }), …
  . . .
  selected : [function(oEvent) {
  this.sWedge1Text = oEvent.getParameter("text");
  this.sWedge1Count = oEvent.getParameter("count");
  }, this]
                    . . .
  });
  . . .
  },
  clickOnWedge1 : function() {
  this.triggerEventOnWedge1("click");
  },
  . . .
  triggerEventOnWedge1: function(sEventType){
  var oEvent = new Event(sEventType);
  var oWedge1 = this.getWedge1();
  oWedge1.dispatchEvent(oEvent);
  },
  getWedge1: function(){
  return jQuery(".sapHpaGrcaudFindingTileChart-part")[0];
  }
  });
  . . .
  test("when user clicks on wedge 1 then receive information for wedge 1",
  function() {
  this.clickOnWedge1();
  equal(this.sWedge1Text, "Wedge 1", "wedge 1 text wrong");
  equal(this.sWedge1Count, "1", "wedge 1 count wrong");
  });
  . . .
});




To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. John Patterson

    Hi Paul

    Thanks a lot for sharing, really good stuff.

    A couple of suggestions, maybe easier for people to access your code if it was available as a gist, and easier for users to relate to if you provided a jsbin example of the tests, should only take a couple of minutes.

    Also “advanced SAPUI5 controls” is a little misleading, I was suprised to see a notepad control.

    Cheers

    jsp

    (0) 

Leave a Reply