dsathyakumar/a11y-auditor

View on GitHub
lib/audit/auditInitializer.js

Summary

Maintainability
B
6 hrs
Test Coverage
/* globals window */
'use strict';

//> libraries - core modules jQuery and lodash
var $ = require('jquery');
var _ = require('lodash/core');

//> modules required from within the app
var rulesExecutor = require('../rulesProcessor/rulesExecutor');
var auditRulesCreator = require('../rulesProcessor/auditRulesCreator');
var auditRunner = require('./auditRunner');
var auditRunnerHelper = require('./auditRunnerHelper');
var hasEventListener = require('../utils/hasEvent')();
var visibilityChecker = require('../utils/elemVisibilityChecker');
var enums = require('../enums/enums');

//> if the default getEventListeners API of browser is undefined, use the custom built one
if (typeof window !== 'undefined' && typeof window.getEventListeners === 'undefined') {
    //> initialize our custom event Watcher to all [Nodes]
    hasEventListener();
}

//> hook the element visibility checker to all Nodes of type [Element]
if (typeof window !== 'undefined') {
    visibilityChecker.call(window);
}

//> declaring the variables beforehand - jshint ;)
var _auditRunnerInitializer, _configValidatorAndProcessor, _preProcessRulesConfig;

//> rulesExecutor has the array of rules implementations and the auditRulesCreator is also available
(function(auditRulesCreator, rulesExecutor) {
    //> so push all the rules into the auditRulesCreator
    rulesExecutor.forEach(function(element) {
        auditRulesCreator.addAuditRule(element);
    });
})(auditRulesCreator, rulesExecutor);

//> variables used accross the auditRunner
var _resultObj = {}, // returns the results of the audit run
    _selectorObjs = {}, // maps an ID to the object extracted from the selectors in the rulesConfig
    _selectorRules = {}, // a map that maps the same ID to the array of rules to be skipped
    _hasCompliance = false, // Boolean to indicate whether or not a compliance exists
    _mustRunGlobalRules = false, // Boolean to indicate whether global rules need to be run or not
    _rulesConfig = {}, // Rules Config object contains rules to be ignored for some selectors
    _auditConfig = {}, // Audit Config object contains audit specific configurations
    _displayOptions = enums.displayEnum.ERRWARN; // Default value for the displayEnum

//> This helps the user to run specific rules only, on a particular element & its childNodes
//> function which takes in a selector and an object similar to _rulesConfig (selector -> ['array of rules to run'])
//> Array of rules can be comma separated rule ID's or comma separated rule Names
//> selectorObj can be a valid DOM object or a HTML selector
//> There is no auditConfig object in this case because - only the rules mentioned are run.
//> If for a childNodes, there are no rules mentioned. Then, no rule will be run for it.
function auditOnSpecificRules(selectorObj, rulesConfig) {

    //> performs initializations for every audit start - but there is no auditConfig in this case
    _auditRunnerInitializer(rulesConfig, {});

    //> sanity checker
    var _validatorResult = auditRunnerHelper.validator(selectorObj);

    //> since a single object is passed, iteration in the audit engine will require length property
    selectorObj = [];

    //> check the result of the validation
    if (_validatorResult.isValid) {
        _configValidatorAndProcessor();
    } else {
        throw new Error(_validatorResult.errMsg);
    }

    /*jshint -W030 */
    (((typeof _validatorResult.selector === 'string') || !(_validatorResult.selector instanceof $)) ? (selectorObj.push($(_validatorResult.selector).get(0))) : (selectorObj.push(_validatorResult.selector)));

    //> run the audit on the current selector that was passed - but not on all of similar types (only on 1st element)
    _resultObj = auditRunner({
        selector: selectorObj,
        _hasCompliance: _hasCompliance,
        _mustRunGlobalRules: _mustRunGlobalRules,
        _rulesConfig: _rulesConfig,
        _auditConfig: _auditConfig,
        _selectorObjs: _selectorObjs,
        _selectorRules: _selectorRules,
        _mode: enums.modeEnum.RUNONLY,
        _displayOptions: _displayOptions,
    });

    //> return the results object
    return _resultObj;
}

//> This runs the accessibility audit on a valid HTML Selector string (or) DOM object
//> function which takes in a selector rulesConfig and auditConfig object
//> 'selector' is a valid DOM [object] or a valid HTML Selector
//> 'rulesConfig' : object containing valid selector and array of rules to ignored
//> For eg: {‘img’ : [‘AX_01’, 'AX_04']}
//> 'auditConfig' object contains a11y-auditor specific config options like 'executeGlobalRules' and 'compliance'
//> These tell the a11y-auditor whether or not to run global rules and what compliance must be tested
//> global rules are ignored by default. To execute them auditConfig.executeGlobalRules must be 'true'
//> if no compliance is passed, all valid rules are run for the element
function auditSelectorOrDomObject(selector, rulesConfig, auditConfig) {

    //> performs initializations for every audit start
    _auditRunnerInitializer(rulesConfig, auditConfig);

    //> sanity checker
    var _validatorResult = auditRunnerHelper.validator(selector);

    //> check the result of the validation
    if (_validatorResult.isValid) {
        _configValidatorAndProcessor();
    } else {
        throw new Error(_validatorResult.errMsg);
    }
    //> check if a jquery object is already being passed. Don't wrap twice.
    /*jshint -W030 */
    ((typeof _validatorResult.selector === 'string') || !(_validatorResult.selector instanceof $) ? (selector = $(_validatorResult.selector)) : (selector = _validatorResult.selector));
    //> run the audit on the current selector that was passed
    _resultObj = auditRunner({
        selector: selector,
        _hasCompliance: _hasCompliance,
        _mustRunGlobalRules: _mustRunGlobalRules,
        _rulesConfig: _rulesConfig,
        _auditConfig: _auditConfig,
        _selectorObjs: _selectorObjs,
        _selectorRules: _selectorRules,
        _mode: enums.modeEnum.RUN,
        _displayOptions: _displayOptions,
    });

    //> return the results object
    return _resultObj;
}

function auditHTMLResponse() {

}

//> function that is run at the call of every invocation of the auditRunner
_auditRunnerInitializer = function _auditRunnerInitializer(rulesConfig, auditConfig) {
    //> for every invocation of the auditor, the result object has to be unique.
    //> No aggregation of results
    _resultObj = {}, _selectorObjs = {}, _selectorRules = {},
    _hasCompliance = false, _rulesConfig = rulesConfig,
    _auditConfig = auditConfig,
    /*jshint -W030 */
    _mustRunGlobalRules = false;
};

//> This function actually begins the process of conducting the audit
_configValidatorAndProcessor = function _configValidatorAndProcessor() {

    //> if _rulesConfig is not null / empty & not undefined
    if (!_.isEmpty(_rulesConfig) && !_.isUndefined(_rulesConfig)) {

        // check if its a valid object & if the contents of the object are valid 'String':Array[] maps
        if (_.isObject(_rulesConfig) && auditRunnerHelper.hasValidElements(_rulesConfig)) {
            //> pre-process rules that need to be ignored - do tasks
            //> on the config options object that is passed
            _preProcessRulesConfig();
        }else {
            throw new Error('Either Configuration options passed is not a valid object (or) its contents wasnt having valid String type as keys (or) the list of rules to skip were not of type Array.');
        }
    }

    //> execute global Rules only when the auditConfig.executeGlobalRules is TRUE & Not Undefined
    if (!_.isUndefined(_auditConfig) && _.isObject(_auditConfig)) {

        //> check if a compliance exists and if its a proper string (or) an Array, it can be let
        if (_.has(_auditConfig, 'compliance') && (_.isString(_auditConfig.compliance) || _auditConfig.compliance instanceof Array)) {
            _hasCompliance = true;
        }

        //> check if the key to execute global rules exists &
        //> if its a proper boolean
        if (_.has(_auditConfig, 'executeGlobalRules') && _.isBoolean(_auditConfig.executeGlobalRules)) {
            _mustRunGlobalRules = _auditConfig.executeGlobalRules;
        }

        //> display options are checked here. The user can request for Errors (or) Errors and Warnings
        if (_.has(_auditConfig, 'displayOptions') && _.isString(_auditConfig.displayOptions)) {
            _displayOptions = auditRunnerHelper.getDisplayEnum(_auditConfig.displayOptions);
        }
    }
};

//> This performs some tasks on the rulesConfig
//> This creates two maps ->
//> 1) a list of objects extracted from the selectors mentioned in the config object.
//> 2) A second map that contains the list of rules
//> The keys for both the maps are the same -> object.toString() + Random Alphanumeric
//> There is no check done here to see if any rule sent as input in the rulesConfig actually is for the particular selector
_preProcessRulesConfig = function _preProcessRulesConfig() {
    var _obj, _ct, _rand;
    //> the config is a 'selector' -> ['array of rules to be skipped']
    for (var currentSelector in _rulesConfig) {
        if (_rulesConfig.hasOwnProperty(currentSelector)) {
            //> get the array of corresponding objects
            _obj = $(currentSelector).get();
            //> go ahead only when there is an object corresponding to the selector
            if (_obj.length > 0) {
                //> non-breaking for loop as it needs to process all the objects
                for (_ct = 0; _ct < _obj.length; _ct++) {
                    //> generate a UID
                    _rand = auditRunnerHelper.UIDGenerator(10, '0123456789abcdefghijkLMNOPQRSTUVWXYZ');
                    //> map the array of objects
                    _selectorObjs[_obj[_ct].toString() + _rand] = _obj[_ct];
                    //> map the array of rules
                    _selectorRules[_obj[_ct].toString() + _rand] = _rulesConfig[currentSelector];
                }
            }
        }
    }
};

//> Export the following two options. More options on the way from here ;)
module.exports = {
    run: auditSelectorOrDomObject,
    runOnly: auditOnSpecificRules,
    auditHTMLResponse: auditHTMLResponse
};