0

i'm trying to find a way to test events. Like in topic title my stack is RequireJs + Karma + Jasmine. I dont want to use jQuery or any not essential external libraries.

There's icon-utils code i use in my navbar: icon -utils.js

define('icon-utils', [], function () {
  return {
    toggleIcon: function (icon, baseIcon, toggledIcon) {
      if (baseIcon === icon.getAttribute("data-icon")) {
        icon.setAttribute("data-icon", toggledIcon);
      } else {
        icon.setAttribute("data-icon", baseIcon);
      }
    }
  };
});

And test of it:

define(['icon-utils'], function(iconUtils) {
  describe('test iconUtils', function () {
    let toggledIcon = 'toggledIcon';
    let baseIcon = 'unToggledIcon';

    it('should change icon to toggled', function() {
      let icon = document.createElement("i");
      icon.setAttribute("data-icon", baseIcon)

      iconUtils.toggleIcon(icon, baseIcon, toggledIcon);
      expect(icon.getAttribute("data-icon")).toEqual(toggledIcon)
    });

    it('should change icon to unToggled', function() {
      let icon = document.createElement("i");
      icon.setAttribute("data-icon", toggledIcon)

      iconUtils.toggleIcon(icon, baseIcon, toggledIcon);
      expect(icon.getAttribute("data-icon")).toEqual(baseIcon)
    });
  })
})

Now there is my nabar.js i want to test

define('navbar', ['icon-utils'], function (iconUtils) {
  Array.from(document.getElementsByClassName("jb-navbar-menu-toggle")).forEach(
    (el) => {
      el.addEventListener("click", (e) => {
        console.log("clicked");
        const dropdownIcon = e.currentTarget
          .getElementsByClassName("icon")[0]
          .getElementsByClassName("material-icons")[0];

        document
          .getElementById(e.currentTarget.getAttribute("data-target"))
          .classList.toggle("is-active");

        iconUtils.toggleIcon(dropdownIcon, "more_vert", "close");
      });
    }
  );
});

And yeah i need to test if class of selected dom element is changing on click:

define(['navbar'], function(navBar) {
  describe('test navBar', function () {
    
      it('should toggle is-active class on click ', function() {
      });
    })
})

I spent a lot of time looking for good solution, but i couldn't find anything helpful. I need to find a way to attach event listener from navbar.js to dom element and check if event was triggered and if is-active class was toggled.

Also as there is not much good sources of testing pure Js i would appreciate every tips that will help me keep good practices and write quality tests.

There is also my karma.conf.js if it is any helpful:

// Karma configuration
// Generated on Tue Feb 09 2021 08:09:01 GMT+0100 (Central European Standard Time)

module.exports = function (config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '../..',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine', 'requirejs'],


    // list of files / patterns to load in the browser
    files: [
      'tests/Ecommerce.Admin.Tests/test-main.js',
      {pattern: 'src/Ecommerce.Admin/wwwroot/js/**/*.js', included: false},
      {pattern: 'tests/Ecommerce.Admin.Tests/js/**/*.spec.js', included: false},
    ],


    // list of files / patterns to exclude
    exclude: [],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {},


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_DEBUG,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity,

    captureTimeout: 210000,
    browserDisconnectTolerance: 3,
    browserDisconnectTimeout : 210000,
    browserNoActivityTimeout : 210000,
  })
}

1 Answer 1

0

The problem is that navbar module attaches event listeners while defining itself.

I had similar problem in my previous job.

There are two possible solutions.

First

First one, is pretty simple, it does not require changing the code of the navbar module, but requires some hacks in the test module.

  1. Define a test module which does not have the tested module as dependency - this is critical
  2. In the test case, add valid html to the document
  3. Require the tested module
  4. Trigger the events
  5. Check the results
  6. Tear down - undef the tested module and clear the document html

Here is pseudo code:

define(function() {
  beforeEach(function(done) {
    document.body.innerHtml = 'put your html here'
    require(['navbar'], function() {
      done() // RequireJS is async so we need to give Jasmine information that the beforeEach section is finished
    })
  })

  beforeEach((done) => {
    document.body.innerHtml = ''
    require.undef('navbar')
  })

  describe('test navBar', function () {
      it('should toggle is-active class on click ', function() {
        // trigger click
        // do assertions
      });
    })
})

Second

Second method will require changes in the code of the module. Instead of attaching event listeners while defining the module, you can return a function which will allow to attach listeners on demand.

Code for the module:

define('navbar', ['icon-utils'], function (iconUtils) {
  return () => {
  Array.from(document.getElementsByClassName("jb-navbar-menu-toggle")).forEach(
    (el) => {
      el.addEventListener("click", (e) => {
        console.log("clicked");
        const dropdownIcon = e.currentTarget
          .getElementsByClassName("icon")[0]
          .getElementsByClassName("material-icons")[0];

        document
          .getElementById(e.currentTarget.getAttribute("data-target"))
          .classList.toggle("is-active");

        iconUtils.toggleIcon(dropdownIcon, "more_vert", "close");
      });
    }
  );
  });
});

Testing will be much easier:

define(['navbar'], function(navbar) {
  beforeEach((done) => {
    document.body.innerHtml = ''
  })

  describe('test navBar', function () {
      it('should toggle is-active class on click ', function() {
        document.body.innerHtml = 'put your html here'
        navbar()
        // trigger click
        // do assertions
      });
    })
})

Not the answer you're looking for? Browse other questions tagged or ask your own question.