Novelty Palliates Malaise

    cocholate

    3.1.0 • Public • Published

    cocholate

    "Why cocholate? Because it goes well with vanilla." -- cocholate's PR.

    cocholate is a small library for DOM manipulation. It's meant to be small, easily understandable and fast.

    Current status of the project

    The current version of cocholate, v3.1.0, is considered to be stable and complete. Suggestions and patches are welcome. Besides bug fixes, there are no future changes planned.

    cocholate is part of the ustack, a set of libraries to build web applications which aims to be fully understandable by those who use it.

    Installation

    The dependencies of cocholate are two:

    cocholate is written in Javascript. You can use it in the browser by sourcing the dependencies and the main file:

    <script src="dale.js"></script>
    <script src="teishi.js"></script>
    <script src="cocholate.js"></script>

    Or you can use these links to the latest version - courtesy of jsDelivr.

    <script src="https://cdn.jsdelivr.net/gh/fpereiro/dale@3199cebc19ec639abf242fd8788481b65c7dc3a3/dale.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/fpereiro/teishi@31a9cf552dbaee79fb1c2b7d12c6fad20f987983/teishi.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/fpereiro/cocholate@2847e66b2ca71859d648b09b699df75d347c3e01/cocholate.js"></script>

    cocholate is exclusively a client-side library. Still, you can find it in npm: npm install cocholate

    Browser compatibility has been tested in the following browsers:

    • Google Chrome 15 and above.
    • Mozilla Firefox 3 and above.
    • Safari 4 and above.
    • Internet Explorer 6 and above.
    • Microsoft Edge 14 and above.
    • Opera 10.6 and above.
    • Yandex 14.12 and above.

    The author wishes to thank Browserstack for providing tools to test cross-browser compatibility.

    Loading cocholate

    As soon as you include cocholate, it will be available on window.c.

    var c = window.c;

    A couple notes regarding polyfills:

    • If cocholate detects that the DOM method insertAdjacentHTML is not defined, cocholate will set it (this will only happen in Firefox 7 and below and Safari 3 and below).
    • Because cocholate uses teishi, the indexOf method for arrays will also be set (this will happen only in Firefox 1, Edge 12 and below and Internet Explorer 8 and below).

    Selectors

    c is the main function of the library. It takes a selector and an optional fun.

    Let's go with the simplest case:

    // This code will return an array with all the divs in the document.
    c ('div');
    // This code will return an array with all the elements that have the class .nav.
    c ('.nav');

    Whenever you pass a string as a selector and no other arguments, cocholate simply uses the native document.querySelectorAll, with the sole difference that returns an array instead of a NodeList.

    A very important exception is when you pass a string selector that targets an id, such as #hola or div#hola, you won't get an array - instead, you'll get either the element itself, or undefined:

    // This code will return the div with id `hola`
    c ('#hola');
    
    // This code will do the same thing.
    c ('#hola');
    
    // This code, however, won't do the same thing, though it means the same thing.
    c ('body #hola');
    
    // This code will return an array, since it targets the children of an element.
    c ('#hola p');

    If you invoke c with the string 'body' as the selector, you will receive only the body itself as the result, instead of an array containing the body.

    c ('body') === document.body // this line will be true

    Note: in old browsers that do not support querySelectorAll (Firefox 3 and below, Internet Explorer 7 and below), cocholate provides a limited variety of selectors, with the following shapes: TAG, #ID, .CLASS, TAG#ID and TAG.CLASS. In these old browsers, if you use a selector that does not conform to these specific forms, cocholate will print an error and return false; in particular, the following characters are forbidden in selectors: ,>[].

    If instead of searching from all elements you want to search within a specific element, instead of a string selector you can use an object with the form {selector: SELECTOR, from: FROM}, where SELECTOR is the string selector and FROM is an DOM element. For example:

    // This will return all divs with class `hello` from the body
    c ({selector: 'div', from: c ('body')});
    
    // This will return all paragraphs with class `hello` from a div with id `hello`
    c ({selector: 'div', from: document.getElementById ('hello')});
    
    // This is equivalent to the last thing we did
    c ({selector: 'div', from: c ('div#hello')});

    If you want to use the logical operations and, not and or, you can do so by using a selector that's an array where the first element is either ':and', ':or' or ':not'.

    // This code will return an array with all the elements that are `div` or `p`
    c ([':or', 'div', 'p']);
    
    // This code will return an empty array (because there are no elements that can be simultaneously a `div` and a `p`).
    c ([':and', 'div', 'p']);
    
    // This code will return an array with all the elements that are neither `div` or `p`.
    c ([':not', 'div', 'p']);

    You can also nest the selectors to an arbitrary degree, as long as the first element of each nested array selector is one of ':and', ':or' or ':not':

    // This code will return an array with all the elements that are `div` or `p` and are contained inside `body`.
    c ([':and', 'body *', [':or', 'div', 'p']]);

    A subtle point: when you pass multiple elements to a ':not' selector, it is actually equivalent to using the ':or' selector. For example, [':not', 'div', 'p'] is equivalent to writing [':not', [':or', 'div', 'p']]. The reason for this disambiguation is that and and or are operations on two operands, where not is an unary operation. The choice of or instead of and reflects what I believe is the most intuitive and common usage of not for multiple operands.

    // These two calls will return the same result.
    c ([':not', 'div', 'p']);
    c ([':not', [':or', 'div', 'p']]);

    If you want to perform an operation on a certain DOM element, you can directly pass it to c.

    // These two calls are equivalent.
    c ('#hello');
    c (document.getElementById ('hello'));

    Note that in this case c will return a single result, instead of an array of results, because the DOM element is only one.

    fun

    Besides returning an array of DOM elements, we will want to do some operations on them. To do this, we can pass a second argument to c, which is a function that will be executed for every element that matched the selector. The results will be collected on an array that's then returned.

    For example, the following call will return an array with the ids of all the divs in the document.

    c ('div', function (e) {
       return e.getAttribute ('id');
    });

    All the following DOM functions are implemented as underlying calls to c, passing its specific logic as the second argument to c.

    DOM functions

    All the functions presented in this section take a selector as its first element and execute its logic for each of the elements matching the selector.

    c.empty

    c.empty removes all the DOM elements within the elements matched by the selector. In other words, it completely gets rid of all the DOM elements nested inside of the matching elements. This function has no meaningful return value. If an invalid selector was passed to this function, an error will be printed.

    c.fill

    c.fill takes html (an HTML string) as its second argument and then fills it with the provided HTML string. This function has no meaningful return value. If an invalid selector was passed to this function, an error will be printed.

    c.place

    c.place takes where as its second argument and html as its third. where can be one of 'beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd', and html is an HTML string. This function has no meaningful return value. Its functionality is based on that of insertAdjacentHTML.

    To explain what this function does, an example serves best. If you start with the following HTML:

    <div id="vamo">
    </div>

    If you would execute the following function call:

    c.place ('#vamo', 'beforeBegin', '<p>beforeBegin</p>');

    You would end up with the following HTML.

    <p>beforeBegin</p>
    <div id="vamo">
    </div>

    If you then do the following three calls:

    c.place ('#vamo', 'afterBegin', '<p>beforeBegin</p>');
    c.place ('#vamo', 'beforeEnd', '<p>beforeEnd</p>');
    c.place ('#vamo', 'afterEnd', '<p>afterEnd</p>');

    You will get:

    <p>beforeBegin</p>
    <div id="vamo">
    <p>afterBegin</p>
    <p>beforeEnd</p>
    </div>
    <p>afterEnd</p>

    c.get

    c.get is useful for fetching attributes from elements. It takes attributes as its second argument (which can be undefined, a string or an array of strings, each of them representing an attribute name) and an optional boolean third parameter css which marks whether you want to get CSS properties instead of DOM ones.

    For each of the matching elements, this function will return an object where the key is the attribute name and the corresponding value is the attribute value. All these objects are wrapped in an array (with the sole exception of a selector that targets an id).

    For example, if you have the following HTML:

    <p id="a" class="red"></p>
    <p id="b" class="blue"></p>
    <p id="c" class="green"></p>

    And you run this code:

    c.get ('p', 'class');

    You'll get an array with three objects: [{class: 'red'}, {class: 'blue'}, {class: 'green'}].

    If you run this code:

    c.get ('p', ['id', 'class']);

    You'll get an array with three objects but two properties each: [{id: 'a', class: 'red'}, {id: 'b', class: 'blue'}, {id: 'c', class: 'green'}].

    As with c, if you pass a selector that targets the id of an element, you will get the attributes themselves without them being wrapped in an array:

    c.get ('#a', 'class');  // will return {class: 'red'}
    c.get ('p#a', 'class'); // will also return {class: 'red'}

    Using the css attribute, you can obtain the CSS properties of an element. Consider this example:

    <p style="color: red;"></p>
    <p style="color: blue;"></p>
    <p style="color: green;"></p>
    c.get ('p', 'color', true);

    If you run this code on the above HTML, you will obtain an array with three objects: [{color: 'red'}, {color: 'blue'}, {color: 'green'}].

    Finally, either with normal attributes or CSS ones, if the attribute is not present, you will get null as its value. For example:

    c.get ('p', 'name');         // will return `[{name: null}]`
    c.get ('p', 'height', true); // will return `[{height: null}]`

    If attributes is undefined, all the attributes will be returned, except those with falsy values (like null, '', false, 0 and false). If you want to bring all CSS attributes, you can explicitly pass undefined as a second argument; note that this will bring only the inline CSS attributes, and not the computed CSS values for the element.

    c.set

    This function is similar to c.get, except that it sets the attributes instead of getting them. This function has no meaningful return value.

    This function takes a selector as first argument, and as second argument an object with all the properties you wish to set. An optional css flag is the third argument, in order to set inline CSS properties.

    If you have the following HTML:

    <p>
    </p>
    c.set ('p', {class: 'someclass'});

    The HTML will look like this:

    <p class="someclass">
    </p>

    To remove an attribute, just pass null as the attribute value. If you execute this code:

    c.set ('body p', {class: null});

    The HTML will go back to its original state:

    <p>
    </p>

    If you pass a truthy third argument, you'll set/unset CSS properties instead.

    c.set ('body p', {color: 'red'}, true);

    Now the HTML will look like this:

    <p style="color: red;">
    </p>

    To remove the style property, you can also use null as a value:

    c.set ('body p', {color: null}, true);

    Now the HTML will look like this:

    <p style="">
    </p>

    By default, if the element whose attribute is being modified has an onchange event handler, the onchange event will be automatically triggered. For example:

    <input id="hello">
    <script>
       c ('#hello').onchange = function () {
          alert (this.value);
       }
    
       c.set ('#hello', {value: 2});
    </script>

    Because of the event handler that we assigned to #hello, we will see an alert with the value 2.

    This also is the case with CSS events. For example, this call to c.set will also trigger the onchange event:

    <input id="hello">
    <script>
       c ('#hello').onchange = function () {
          alert (this.style.color);
       }
    
       c.set ('#hello', {color: 'lime'}, true);
    </script>

    If you want to override this behavior, you can simply pass a truthy fourth argument:

    // for normal attributes
    c.set ('#hello', {value: 2}, false, true);
    
    // for CSS attributes
    c.set ('#hello', {color: 'lime'}, true, true);

    c.fire

    This function creates an event and triggers it on the specified elements. It takes an eventType as its second argument, a string that determines which argument is fired (for example, 'click'). This function has no meaningful return value.

    c.fire ('#button', 'click');

    c.fire is useful for test scripts that simulate user interactions.

    Non-DOM functions

    Besides the six DOM functions, there are five more for a few things that are convenient to have around.

    c.ready

    A function that gets executed when the HTML page and all its resources (including stylesheets and scripts) have finished loading. Takes a single argument, fun, containing the code to be executed when this event happens.

    This function is handy to prevent executing your application code before all scripts are loaded. This will happen automatically on most browsers if you place all your scripts at the bottom of the body - the exception is Internet Explorer 8 and below, which seem to run the scripts in parallel.

    This function is also handy if you want to wait for all stylesheets to load before executing your script.

    c.cookie

    Quick & dirty cookie parsing. Takes an optional argument, cookie, a cookie string that will be parsed. If you don't pass any arguments, this function will read the cookie at document.cookie instead.

    This function returns an object with keys/values, each of them pertaining to a property of the cookie.

    If you pass false as the argument, c.cookie will delete all the cookies that are accessible to javascript - that is, those that don't have the HttpOnly directive/attribute.

    c.ajax

    This function can make ajax calls and provides a few conveniences.

    It takes five arguments:

    • method: a string. Defaults to 'GET' if you pass a falsy argument.
    • path: a string with the target path.
    • headers: an object where every value is a string. Defaults to {} if you pass a falsy argument.
    • body: the body of the request. Defaults to '' if you pass a falsy argument.
    • callback: a function to be executed after the request is completed. Defaults to an empty function if you pass a falsy argument.

    The conveniences provided are:

    • You can directly pass a FormData object, useful for multipart/form-data requests.
    • If you pass an array or object as body, the content-type header will be automatically set to application/json and the body will be stringified.
    • All ajax requests done through this function are asynchronous.
    • The function will synchronously return an object of the form {headers: ..., body: ..., xhr: <the request object>} (corresponding to the request data).
    • If the response has a code 200 or 304, the callback will receive null as its first argument and the following object as the second argument: {headers: {...}, body: ..., xhr: <the request object>}. If the Content-Type response header is application/json, the body will be parsed - if the body turns out to be invalid JSON, its value will be false.
    • If the code is not 200 or 304, the request object will be received as the first argument. The request object contains all the relevant information, including payloads and errors.

    c.loadScript

    This function requests a script and places it on the DOM, at the bottom of the body. It takes two arguments: src, the path to the javascript file; and an optional callback that is executed after the script is fetched. If callback is not passed, it will default to an empty function.

    c.loadScript uses c.ajax to retrieve the script asynchronously and will return the result of its invocation to c.ajax - which will be false if src is invalid, and a request object otherwise.

    If the script is successfully fetched, callback will receive two arguments (null and the request object); in case of error, it will receive the request object as its first argument.

    c.test

    This function allows to define and execute tests. It's meant as an ultra lightweight yet effective test runner. This function takes one argument only, tests, which is an array. Each of the elements contained by tests should also be an array. The two possible forms for each test array is:

    • [TAG, ACTION, CHECK]
    • [TAG, CHECK]

    TAG is a string which prints the name of the test being performed. CHECK is a synchronous function that performs a check; if the check fails, the function should return false - this will make c.test throw an error. Any other value returned by CHECK will signify a successful check and the sequence of tests will continue executing.

    ACTION is a potentially asynchronous function that performs an action. It will be executed before CHECK. If this function returns a value other than undefined, it will be considered synchronous and CHECK will be executed immediately afterwards. If you however wish to perform an async operation, you can do so and not return any value. When the async operation is done, use the next function passed as the first argument to ACTION, which is the callback. If you wish to wait n milliseconds before CHECK gets called, pass a positive integer to next - this is equivalent as writing setTimeout (next, <milliseconds>).

    c.test will execute all tests in sequence and stop at the first error. It will print the TAG for each test about to be executed. If the test suite has executed successfully, c.test will print a message.

    If c.test receives an invalid tests array, it will print an error and return false. Otherwise, the function will return undefined. Note that, whether the test suite fails or succeeds, c.test will return undefined - false only denotes invalid tests.

    prod mode

    cocholate's functions spend most of its running time (easily 80-90%) performing validations to their inputs. While validation is essential to shorten the debug cycle when developing, in certain cases you might want to turn it off to improve performance. This can be done by enabling prod mode. To do this, set c.prod to true.

    The cost of turning off validation is that if there's an invalid invocation somewhere, an error will be thrown.

    Source code

    The complete source code is contained in cocholate.js. It is about 340 lines long.

    Below is the annotated source.

    /*
    cocholate - v3.1.0
    
    Written by Federico Pereiro (fpereiro@gmail.com) and released into the public domain.
    
    Please refer to readme.md to read the annotated source.
    */

    Setup

    We wrap the entire file in a self-executing anonymous function. This practice is commonly named the javascript module pattern. The purpose of it is to wrap our code in a closure and hence avoid making the local variables we define here to be available outside of this module.

    (function () {

    If we're in node.js, we print an error and return undefined.

       if (typeof exports === 'object') return console.log ('cocholate only works in a browser!');

    We require dale and teishi. Note that, in the browser, dale and teishi will be loaded as global variables.

       var dale   = window.dale;
       var teishi = window.teishi;

    We create an alias to teishi.type, the function for finding out the type of an element. We do the same for teishi.clog, a function for printing logs that also returns false. We also do the same for teishi.inc, a function for checking whether a given element is contained in an array.

       var type = teishi.type, clog = teishi.clog, inc = teishi.inc;

    Polyfill for insertAdjacentHTML

    We will define a polyfill for insertAdjacentHTML, which will be necessary in old versions of Safari and Firefox. It is based on Eli Grey's polyfill.

    We set the function only if it's not defined. The function takes two arguments, position and html.

       if (! document.createElement ('_').insertAdjacentHTML) HTMLElement.prototype.insertAdjacentHTML = function (position, html) {

    We create a container element and then we set its innerHTML property to the html we received as a string. This will create all the desired DOM nodes inside container.

          var container = document.createElement ('div');
          container.innerHTML = html;

    We now iterate the outermost elements inside container. By outermost, I mean that only those elements that are direct children of container will be iterated - whereas elements that are inside these top-level children will not be iterated.

    We're, however, iterating the elements in a rather strange way. Instead of using a for loop or an equivalent functional construct, we're executing the same piece of code as long as container has one element. How can this work without setting us for an infinite loop? Let's see it in a minute.

          while (container.firstChild) {

    If position is beforeBegin, we place container.firstChild before the element, using the insertBefore method on the element's parent.

    Once we do this, container.firstChild (the first element we're positioning) will be in its desired position and not on container anymore. in this way, the next time the while loop runs, container.firstChild will be the second element we want to place - and if there's no second element, the loop will be finished.

             if      (position === 'beforeBegin') this.parentNode.insertBefore (container.firstChild, this);

    If position is afterBegin, we place container.firstChild as the first children of the element using the insertBefore method on the element itself.

             else if (position === 'afterBegin')  this.insertBefore            (container.firstChild, this.firstChild);

    If position is beforeEnd, we merely append container.firstChild to the element.

             else if (position === 'beforeEnd')   this.appendChild             (container.firstChild);

    Finally, if position is afterEnd, we place container.firstChild just after the element using the insertBefore method on the parent.

             else                                 this.parentNode.insertBefore (container.firstChild, this.nextElementSibling)

    There's nothing else to do, so we close the loop and the polyfill function.

          }
       }

    Core

    We define c, the main function of the library. Note we also attach it to window.c, so that it is globally available to other scripts. This function takes two arguments, selector and fun.

    c, besides being a function, will serve as an object that collects the other functions of the library.

       var c = window.c = function (selector, fun) {

    If prod mode is not enabled, we check that fun is a function or undefined. If it is neither, an error is printed and the function returns false.

    Note we pass true as the fourth argument to teishi.stop. We will do this for every invocation of teishi.stop and teishi.v, to tell teishi not to validate our validation rules. This will yield a (very small) performance improvement.

          if (! c.prod && teishi.stop ('c', ['fun', fun, ['function', 'undefined'], 'oneOf'], undefined, true)) return false;

    We create a local variable that will indicate whether the selector is actually a DOM node.

          var selectorIsNode = selector && selector.nodeName;

    We define a local variable elements that will contain all the DOM elements to which selector refers. If the selector is itself a DOM node, we wrap it in an array. Otherwise, the search for all matching elements is done by c.find, a function which we'll see below.

          var elements = selectorIsNode ? [selector] : c.find (selector);

    If c.find returns false, this means that the selector is invalid. In this case, c.find will have already printed an error message. We return false.

          if (elements === false) return false;

    If we're here, the input is valid.

    If fun was passed, we first collect all extra arguments passed to c into an array named args. This array will always exclude selector and fun. If no extra arguments were passed, args will be an empty array.

          if (fun) {
             var args = dale.go (arguments, function (v) {return v}).slice (2);

    We iterate through elements, the DOM elements that match selector. For each of them, we apply them to fun, with each of them as the first argument and further arguments also passed. We collect the results of these function applications into an array and set it to elements.

    This means that if fun is present, elements will contain the results of passing each of elements to fun - whereas if fun is absent, elements will contain the DOM elements themselves.

             elements = dale.go (elements, function (v) {
                return fun.apply (undefined, [v].concat (args));
             });
          }

    If the selector a DOM node, or if it is the string 'body', or if it is of the form #ID or TAGNAME#ID, we return the first (and only) element of elements.

          if (selectorIsNode || selector === 'body' || (type (selector) === 'string' && selector.match (/^[a-z0-9]*#[^\s\[>,:]+$/))) return elements [0];

    Otherwise, we return the entire array of elements. There's nothing else to do, so we close the function.

          return elements;
       }

    c.nodeListToArray is a helper function that converts a NodeList into a plain array containing DOM elements. Whenever the browser returns a NodeList, we convert it into a plain array - this simplifies iteration, especially in old browsers. This function takes a single nodeList as its argument; since we always pass it a valid NodeList, we don't validate its input.

       c.nodeListToArray = function (nodeList) {

    We define an output array.

          var output = [];

    We iterate nodeList with a plain old for loop. We cannot use dale.go here since Safari 5.1 and below consider nodeList to be of type function.

          for (var i = 0; i < nodeList.length; i++) {

    We push each of the elements to output.

             output.push (nodeList [i]);
          }

    We return output and close the function.

          return output;
       }

    We define c.setop, a helper function that performs set operations (and, or and not). This function takes an operation (and|or|not) and two arrays of DOM elements. Each of these sets are compared according to the given operation.

       c.setop = function (operation, set1, set2) {

    If the operation is and, we go through the first set and create a new array filtering out those elements from the first set that are not present on the second set. We return this filtered first set, which represents an intersection of both sets.

          if (operation === 'and') return dale.fil (set1, undefined, function (v) {
             if (inc (set2, v)) return v;
          });

    We copy the first set onto a new array output, since we don't want to modify it.

          var output = set1.slice ();

    If the operation is or, we iterate the elements of the second set. Each of the elements of the second set that are not on the first set will be pushed onto output. or represents an union of both sets.

          if (operation === 'or') {
             dale.go (set2, function (v) {
                if (! inc (output, v)) output.push (v);
             });
          }

    If we're here, the operation is not. In this case, we want to substract the second set from the first.

          else {

    If the first set is empty, we put in output all the elements from the document.

             if (output.length === 0) output = c.nodeListToArray (document.getElementsByTagName ('*'));

    We remove from output all the elements that are present in the second set.

             dale.go (set2, function (v) {
                var index = output.indexOf (v);
                if (index > -1) output.splice (index, 1);
             });
          }

    We return output and close the function.

          return output;
       }

    c.find is a function that resolves a cocholate selector into a set of DOM elements. It is used by the main (c) function to find elements. It takes a single argument, selector.

       c.find = function (selector) {

    We get the type of selector.

          var selectorType = type (selector);

    selector must be either an array, a string or an object.

          if (! c.prod && teishi.stop ('cocholate', [
             ['selector', selector, ['array', 'string', 'object'], 'oneOf'],

    If selector is an array, its first element must be a string with a colon plus one of the operations and, or and not.

             function () {return [
                [selectorType === 'array',  ['first element of array selector', selector [0], [':and', ':or', ':not'], 'oneOf', teishi.test.equal]],

    If selector is an object, its keys must be selector and from. selector.selector must be an array or string.

                [selectorType === 'object', [
                   ['selector keys', dale.keys (selector), ['selector', 'from'], 'eachOf', teishi.test.equal],
                   ['selector.selector', selector.selector, ['array', 'string'], 'oneOf'],

    Now we validate selector.from. We expect it to be a DOM element. If the browser supports querySelectorAll, we check that the DOM element is valid by testing whether the querySelectorAll element exists for the given selector.from.

    An implementation note: we write this last validation rule as a function and not an array because Internet Explorer 8 and below throw a strange error when placing DOM elements within a teishi array rule.

                   function () {
                      if (type (selector.from) !== 'object' || (document.querySelectorAll && ! selector.from.querySelectorAll)) return clog ('c.find', 'selector.from passed to cocholate must be a DOM element.');
                      return true;
                   }

    If any of the validations fail, we print an error and return false.

                ]]
             ]}
          ], undefined, true)) return false;

    First we'll cover the cases where selector is either a string or an object.

          if (selectorType !== 'array') {

    If the browser supports querySelectorAll (which should happen for any of the browsers we support except Firefox 3 and below and Internet Explorer 7 and below) and selector is a string, we merely invoke document.querySelectorAll on it, convert the NodeList into an array, and return it.

          if (document.querySelectorAll && selectorType === 'string') return c.nodeListToArray (document.querySelectorAll (selector));

    If selector is an object, we invoke querySelectorAll on selector.from (which is a DOM element) and we use selector.selector as the selector. We also convert the NodeList into an array and return it.

             if (document.querySelectorAll && selectorType === 'object') return c.nodeListToArray (selector.from.querySelectorAll (selector.selector));

    If we're here, we're still dealing with a string or object selector, but on either Firefox 3 and below or Internet Explorer 7 and below. This is where it gets fun. In this section, we'll write code to provide limited selector support to these old browsers.

    We define a variable from that will be the context for selecting DOM elements. It will be selector.from (if defined) or document (if selector is a string).

             var from = selector.from ? selector.from : document;

    If selector is an object, we reassign it to selector.selector.

             selector = selectorType === 'string' ? selector : selector.selector;

    We are going to provide limited support for selectors; namely, we will only support selectors of these shapes: TAG, TAG#ID, TAG.CLASS, #ID, .CLASS. Note that we also support *, since it's possible to pass a wildcard to document.getElementsByTagName (which means that all elements will be selected).

    If selector doesn't conform to any of these shapes, we will print an error and return false. We make sure to forbid the characters ,, >, [ and ] since those have special meaning on modern DOM selectors.

             if (selector !== '*' && ! selector.match (/^[a-z0-9]*(#|\.)?[^,>\[\]]+$/i)) return clog ('The selector ' + selector + ' is not supported in IE <= 7 or Firefox <= 3.');

    If we're here, selector is supported. We will now determine what's the criterium for selecting elements; if there's a # in the selector, it will be by id; if there's a ., it will be by class. If there's neither, we'll set it to undefined (in which case it means that we will select elements by tag).

             var criterium = selector.match ('#') ? 'id' : (selector.match (/\./) ? 'class' : undefined);

    We split selector by either # or ..

             selector = selector.split (/#|\./);

    We define tag, a variable that will indicate whether we filter our elements to belong to a certain tag name. If selector was split in two (because there's either a hashtag or a dot), we'll set tag to its first element; if it was not split in two (which means absence of both hashtag and dot), we'll also set tag to its first element. If selector has length 1 and there's a class or hashtag present, then tag will be undefined (because selector will only contain class or id information).

    Note that, if present, we convert tag to uppercase since browsers expect it to be uppercase.

             var tag = (selector.length === 2 || ! criterium) ? selector [0].toUpperCase () : undefined;

    We invoke getElementsByTagName on from; if a specific tag is required, we pass it as an argument to this function; otherwise, we pass a wildcard to get all the elements. Now, if selector.from was passed, we want only the child elements of from to be selected; if selector.from is absent, then we'll select elements from all the elements in the document.

    The invocation to getElementsByTagName returns a NodeList. We convert it to an array of DOM elements with c.nodeListToArray. Once we have this array of elements, we iterate them, filtering out those for which the iterating function returns undefined. In other words, the function we're about to define (which takes one node at a time), will determine whether the iterated element is selected.

             return dale.fil (c.nodeListToArray (from.getElementsByTagName (tag || '*')), undefined, function (node) {

    If we're selecting elements by class and this element's class doesn't match it, we ignore the element. Note we split node.className (if it exists) by whitespace into an array of classes, and make sure that the class we're looking for is one of the elements of that array.

                if (criterium === 'class' && ! inc ((node.className || '').split (/\s/), teishi.last (selector))) return;

    If we're selecting an element by id and the element's id doesn't match the id we're looking for, we ignore the element.

                if (criterium === 'id'    && node.id !== teishi.last (selector)) return;

    If we're here, we will return the element since it matches the required criteria.

                return node;

    We close the iteration function and also this block, since there's nothing left to do.

             });
          }

    If we're here, selector is an array. This means that selector contains logical operations (and/or/not). We will start by extracting operation, which is the logical operation that is the first element of the selector array. We'll also define output, an array where we'll collect the matching elements.

          var operation = selector.shift (), output = [];

    We iterate selector, which contains a number of selectors that could be themselves arrays, objects or strings. We will stop whenever any of these iterations returns false.

    The approach we take is to iterate the selectors and apply logical operations onto the output set incrementally as we iterate through the selectors.

          dale.stop (selector, false, function (v, k) {
    

    We do a recursive call to c.find passing it the selector we're iterating. We collect the result of the recursive call in a local variable elements.

             var elements = c.find (v);

    If the recursive call to c.find is false, it means the selector is invalid. We set output to false and return false, which will stop the iteration.

             if (elements === false) return output = false;

    If we're on the first selector and the operation is either and or or, we set output to elements. This initializes output, since before this operation it was empty.

             if (k === 0 && operation !== ':not') output = elements;

    If we're not on the first selector, or we're using the not criterium, we apply the logical operation (through c.setop) with output (the already selected elements) and elements (the new elements to be taken into consideration).

             else                                 output = c.setop (operation.replace (':', ''), output, elements);

    We finish iterating the elements.

          });

    If any of the selectors was false, output will also be false. If all the selectors were valid, output will contain the selected elements. We return it and close the function.

          return output;
       }

    DOM functions

    We now start with the first of our DOM functions. All the DOM functions internally use the c function and the other helper functions we have seen. Let's start with c.empty, which deletes all the child elements within the selected elements. This function takes a single argument, a selector.

       c.empty = function (selector) {

    We call c with selector and a function as arguments. This function will be executed for each of the elements that match selector.

          c (selector, function (element) {

    We merely set the element's innerHTML to an empty string.

             element.innerHTML = '';

    There's nothing else to do, so we close the iteration function, the invocation to c and the function. Note that we don't return any values.

             });
          });
       }

    We now define c.fill, which takes selector and html as arguments.

       c.fill = function (selector, html) {

    If html is not a string, the function will print an error message and return false.

          if (! c.prod && teishi.stop ('c.fill', ['html', html, 'string'], undefined, true)) return false;

    We iterate the elements matched by selector (through a call to c) and set their innerHTML property to html.

          c (selector, function (element) {
             element.innerHTML = html;

    There's nothing else to do, so we close the function. Note that we don't return any values.

          });
       }

    We now define c.place, a function that takes three arguments: selector, where and html.

       c.place = function (selector, where, html) {

    We make sure that where is one of four strings: beforeBegin|afterBegin|beforeEnd|afterEnd, and that html is a string. If either of these conditions is not fulfilled, we print an error message and return false.

          if (! c.prod && teishi.stop ('c.place', [
             ['where', where, ['beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd'], 'oneOf', teishi.test.equal],
             ['html', html, 'string']
          ], undefined, true)) return false;

    For each of the elements matching selector, we apply insertAdjacentHTML with where and html as its arguments.

          c (selector, function (element) {
             element.insertAdjacentHTML (where, html);

    There's nothing else to do, so we close the function. Note that we don't return any values.

          });
       }

    We now define c.get, which takes three arguments: selector, attributes and css. The last argument is a flag (presumably boolean), that will determine whether we're referring to CSS attributes or not.

       c.get  = function (selector, attributes, css) {

    If attributes is not string, undefined, nor an array, we print an error and return false.

          if (! c.prod && teishi.stop ('c.get', ['attributes', attributes, ['string', 'array', 'undefined'], 'oneOf'], undefined, true)) return false;

    We define an array ignoredValues with attribute values which we will ignore. This is only necessary for iterating style attributes in Internet Explorer 8 and element attributes in Internet Explorer 7 and below.

          var ignoredValues = [null, '', false, 0, "false"];

    We iterate the elements matched by selector and apply the following function to each of them. Note that we will return an array containing the output of this function for each of the elements.

          return c (selector, function (element) {

    If attributes is not undefined, we iterate it - if attributes is a string, this will be equivalent to having a single attribute. We will create an object with the attributes and return it.

             if (attributes !== undefined) return dale.obj (attributes, function (v) {

    If the css flag is enabled, we'll access element.style [v] (which contains the CSS attribute). If the attribute is not present, we will consider it to be null.

                if (css) return [v, element.style [v] || null];

    If the css flag is disabled, we will instead return the element's attribute (accessed through getAttribute).

                else     return [v, element.getAttribute (v)];
             });

    If we're here, attributes is undefined, which means we want all the element's attributes. If css is falsy, we want the actual attributes (as opposed to the style attributes) of the element. We iterate element.attributes. Note that we start with a base object with the element's class or the element className (if class is absent) - this is only for the benefit of Internet Explorer 7 and below.

             if (! css) return dale.obj (element.attributes, (element ['class'] || element.className) ? {'class': element ['class'] || element.className} : {}, function (v, k) {

    If the attribute is truthy, if its nodeName is truthy, and its nodeValue is not one of the values we are ignoring, we return them both. Checking whether the attribute is truthy is only necessary in Internet Explorer 7 and below; many browsers, however, require us to check whether nodeName is truthy, otherwise undefined attributes will be returned.

                if (v && v.nodeName && ! inc (ignoredValues, v.nodeValue)) return [v.nodeName, v.nodeValue];
             });

    If we're here, we want all inline CSS attributes for the element. In all supported browsers except for Internet Explorer 8, element.style has a length property that we will use to iterate the style object. In Internet Explorer 8 and below, however, we're forced to iterate all the keys of the object.

             return dale.obj (element.style.length ? dale.times (element.style.length, 0) : dale.keys (element.style), function (k) {

    If element.style.length is supported, we simply return the corresponding key and value of the style object.

                if (element.style.length) return [element.style [k], element.style [element.style [k]]];

    For Internet Explorer 8 and below, we return the key and value but only if the value is not one of the ignoredValues.

                if (! inc (ignoredValues, element.style [k])) return [k, element.style [k]];
             });

    There's nothing else to do, so we close the iterating function, the invocation to c and the function itself.

          });
       }

    We now define c.set. It is similar to c.get, but instead of returning attributes, it sets them. It takes four arguments, selector, attributes, css and notrigger - the last two are flags.

       c.set  = function (selector, attributes, css, notrigger) {

    We now validate the input. attributes must be an object.

          if (! c.prod && teishi.stop ('c.set', [
             ['attributes', attributes, 'object'],

    Every attribute key must start with a ASCII letter, underscore or colon, and must follow with zero or more of the following:

    • A letter.
    • An underscore.
    • A colon.
    • A digit.
    • A period.
    • A dash.
    • Any Unicode character with a code point of 129 (0080 in hexadecimal) or above - these include all extended ASCII characters (the top half of the set) and every non-ASCII character.

    This arcana was kindly provided by this article. The regex below was taken from the article and modified to add the permitted Unicode characters.

             [
                ['attribute keys', 'start with an ASCII letter, underscore or colon, and be followed by letters, digits, underscores, colons, periods, dashes, extended ASCII characters, or any non-ASCII characters.'],
                dale.keys (attributes),
                /^[a-zA-Z_:][a-zA-Z_:0-9.\-\u0080-\uffff]*$/,
                'each', teishi.test.match
             ],

    The attribute values must be either integers, floats, strings or null.

             ['attribute values', attributes, ['integer', 'float', 'string', 'null'], 'eachOf']

    If any of these conditions is not met, an error will be printed and the function will return false.

          ], undefined, true)) return false;

    For each of the elements that are matched by selector, we will invoke the following function.

          c (selector, function (element) {

    For each of the elements, we iterate attributes.

             dale.go (attributes, function (v, k) {

    If we're setting a css attribute, we set the attribute within element.style. Note that if its desired value is null, we set the attribute to an empty string.

                if       (css)        element.style [k] = v === null ? '' : v;

    If we're instead setting an HTML attribute, we use either removeAttribute or setAttribute, depending on whether the desired value is null or not.

                else if  (v === null) element.removeAttribute (k);
                else                  element.setAttribute    (k, v);
             });

    If the notrigger flag is absent, we fire a change event on the element through c.fire, which is defined later. This means that by default c.set will trigger a change event for those elements that it matches.

             if (! notrigger) c.fire (element, 'change');

    There's nothing else to do, so we close the function. Note that we don't return any values.

          });
       }

    We now define c.fire, the last DOM function. For each matched element, this function creates an event and dispatches it to the element. This function takes two arguments: selector and eventType.

       c.fire = function (selector, eventType) {

    If eventType is not a string, we print an error and return false.

          if (! c.prod && teishi.stop ('c.fire', ['event type', eventType, 'string'], undefined, true)) return false;

    For each of the elements that are matched by selector, we will invoke the following function.

          c (selector, function (element) {

    We define a local variable ev to hold the event we are about to create.

             var ev;

    We first try to create the event using the Event constructor, which should work for most browsers. Note we pass eventType to the constructor so the event is of the desired type.

             try {
                ev = new Event (eventType);
             }

    If the event constructor is not supported, we use either the createEvent method or the createEventObject method. The latter method is only for Internet 8 and below.

             catch (error) {
                ev = document.createEvent ? document.createEvent ('Event') : document.createEventObject ();

    In all browsers that don't support the constructor and aren't Internet Explorer, we invoke the initEvent method and pass to it eventType. We pass extra false arguments since they're required in old versions of Firefox.

                if (document.createEvent) ev.initEvent (eventType, false, false);
             }

    If the browser supports the dispatchEvent method, (which goes for all the browsers we support except for Internet Explorer 8 and below), we will invoke it, passing the event to it.

             if (element.dispatchEvent) return element.dispatchEvent (ev);

    If the browser doesn't support fireEvent, there's no available method with which to fire the event. In this case, we print an error and return false.

             if (! element.fireEvent) return clog ('c.fire error', 'Unfortunately, this browser supports neither EventTarget.dispatchEvent nor element.fireEvent.');

    For Internet Explorer 8 and below, we instead invoke fireEvent. Note that we pass both eventType and the event itself. Also notice we prepend 'on' to the eventType, so that (for example), click becomes onclick.

    We wrap this statement into a try block because some the combination of some events and node elements throws an error in Internet Explorer 8 and below - for example, a change event on a <div>.

             try {
                element.fireEvent ('on' + eventType, ev);
             }

    If fireEvent throws an error, we detect whether there's a handler for the event. If there is, we execute it. While we could pass ev as an argument to it, arguments seem to be ignored altogether and are not received by the event handlers.

             catch (error) {
                if (element ['on' + eventType]) element ['on' + eventType] ();
             }

    There's nothing else to do, so we close the function. Note that we don't return any values.

          });
       }

    Non-DOM functions

    Our first non-DOM function is c.ready, which takes a single function that will be run when the the HTML page, all scripts and all stylesheets have been loaded.

       c.ready = function (fun) {

    If document.addEventListener is present (which is the case on most browsers), we'll attach fun to it when the load event is triggered. We pass a false third argument (useCapture) because Firefox 5 and Opera 11.5 and below require it.

          if (window.addEventListener) return window.addEventListener ('load', fun, false);

    If we're on Internet Explorer 8 and below, we instead use window.attachEvent and bind it to the onload event.

          if (window.attachEvent)      return window.attachEvent      ('onload', fun);

    If we're on very old browsers (Safari 6 and below, old versions of Chrome & Firefox for Android), neither of these two methods will be present. Instead, we we will run a function every 10 milliseconds until the document is ready.

    Once the document is complete, fun will be run and the function running every 10 milliseconds will stop being called.

          var interval = setInterval (function () {
             if (document.readyState === 'complete') fun () || clearInterval (interval);
          }, 10);

    We close the function.

       }

    We define c.cookie, a function to read and delete cookies. It takes an optional cookie argument.

       c.cookie = function (cookie) {

    If cookie is false, we will delete all cookies from the client. Note that this only will happen for those cookies within the domain on which the page is being served, since a given domain can only access its own cookies.

          if (cookie === false) {

    We take document.cookie, a string which contains all the stored cookies we can access from the current domain. We split it by semicolons, ignoring any whitespace after the semicolons.

             return dale.go (document.cookie.split (/;\s*/), function (v) {

    For each of the cookies, we take the name of the cookie (the text before the = sign), overwrite its value with an empty string and then set its expires property to the present moment. This will make the browser delete the cookie immediately. The approach was taken from here.

                document.cookie = v.replace (/^ +/, '').replace (/=.*/, '=;expires=' + new Date ().toUTCString ())

    We return the deleted cookie.

                return v;

    We close the loop and the block; all the deleted cookies will be returned inside an array.

             });
          }

    If we're here, we're reading cookies instad of deleting them. If cookie is absent, we will read document.cookie instead. We split it by semicolons, ignoring any whitespace after the semicolons.

    After iterating each cookie we will return an object where each key is the cookie name and each value is the cookie value.

          return dale.obj ((cookie || document.cookie).split (/;\s*/), function (v) {

    If the cookie is empty, we ignore it.

             if (v === '') return;

    We split the cookie by the = sign.

             v = v.split ('=');

    We extract the name and the value into variables.

             var name = v [0];
             var value = v.slice (1).join ('=');

    We return name as the key and value as the value for the cookie. There's nothing else to do, so we close the iteration and the function.

             return [name, value];
          });
       }

    We define now c.ajax, a function for performing asynchronous calls, therefore rending web applications (as opposed to mere web pages) truly possible.

    This function takes five arguments: method, path, headers, body and callback.

       c.ajax = function (method, path, headers, body, callback) {

    If method is not present, we will set it to GET.

          method   = method   || 'GET';

    If headers is not present, we will set it to an empty string.

          headers  = headers  || {};

    If body is not present, we will set it to an empty string.

          body     = body     || '';

    If callback is not present, we will set it to an empty function.

          callback = callback || function () {};

    Notice we don't set path to anything if it's absent, since no sensible default can be assumed.

    We make sure that method and path are strings, that headers is an object and that callback is a function. If any of these conditions is not met, we print an error and return false.

          if (! c.prod && teishi.stop ('c.ajax', [
             ['method',   method,   'string'],
             ['path',     path,     'string'],
             ['headers',  headers,  'object'],
             ['callback', callback, 'function']
          ], undefined, true)) return false;

    We initialize the XMLHttpRequest object, which will be present in most browsers. In Internet Explorer 5 and 6, XMLHttpRequest is absent, but we can use ActiveXObject instead.

          var r = window.XMLHttpRequest ? new XMLHttpRequest () : new ActiveXObject ('Microsoft.XMLHTTP');

    We initialize the request, passing it the uppercased method, path and a truthy third argument to indicate that we want the request to be asynchronous.

          r.open (method.toUpperCase (), path, true);

    If body is not a FormData object and it is instead a plain array or an object, we do two things:

          if (teishi.complex (body) && type (body, true) !== 'formdata') {
    1. Set the content-type header to application/json, unless it's already present in headers.
             headers ['content-type'] = headers ['content-type'] || 'application/json';
    1. We set body to its stringified value.
             body = teishi.str (body);
          }

    We set all the headers through setRequestHeader.

          dale.go (headers, function (v, k) {
             r.setRequestHeader (k, v);
          });

    We set an event handler that will be fired when the request goes from one phase to the next.

          r.onreadystatechange = function () {

    If readyState does not equal to 4, the request is not yet complete, so we don't do anything.

             if (r.readyState !== 4) return;

    If the response status is neither 200 nor 304, we consider the request to have failed. In this case, we invoke callback with the request as its first argument, to indicate that an error has happened.

             if (r.status !== 200 && r.status !== 304) return callback (r);

    We define a variable json that we will use to detect whether the response was of type json.

             var json;

    We define a variable res that will hold the response object. We set its xhr key to the actual response object.

             var res = {
                xhr: r,

    We take the response headers (which are a string), split them by newlines and build a headers object by iterating them. In Firefox 18 and below, the response headers don't contain carriage returns (\r), so we make them optional in the regex we pass to split. For each header:

                headers: dale.obj (r.getAllResponseHeaders ().split (/\r?\n/), function (header) {

    If header is an empty string, we ignore it.

                   if (header === '') return;

    We create two variables: name, to hold the name of the header; and value, to hold the contents of the header after the name, a colon and one or more whitespace. The name will be all the text before the first colon.

                   var name = header.match (/^[^:]+/) [0], value = header.replace (name, '').replace (/:\s+/, '');

    If the content-type header matches the application/json MIME type, we set json to true.

                   if (name.match (/^content-type/i) && value.match (/application\/json/i)) json = true;

    We return name and value to place them within headers. This concludes the iteration. We also close headers.

                   return [name, value];
                })
             };

    We set res.body to the responseText property of the request; if the response was JSON, we parse the responseText.

             res.body = json ? teishi.parse (r.responseText) : r.responseText;

    We invoke callback with a null first argument and res as its second. This concludes the handler.

             callback (null, res);
          }

    We submit the request.

          r.send (body);

    We synchronously return an object with headers (the request headers), body (the body sent with the request) and xhr (the request object itself). This concludes the function.

          return {headers: headers, body: body, xhr: r};
       }

    We define c.loadScript, a function that loads an external script. It takes two arguments, src (the path to the script) and callback (the function that is executed after the operation is complete).

       c.loadScript = function (src, callback) {

    If callback is falsy, we set it to an empty function.

          callback = callback || function () {};

    We perform a GET request through c.ajax, passing src as the path. Note we return the result of the invocation to c.ajax, so that the request object is available outside of the function invocation.

          return c.ajax ('get', src, {}, '', function (error, data) {

    If there was an error, we pass it to callback.

             if (error) return callback (error);

    If we're here, the request was successful. We create a script element.

             var script = document.createElement ('script');

    We set the text of script to the body of the response. We do this within a try/catch block since Internet Explorer 8 and below don't support the first method. The code was adapted from this snippet.

             try {
                script.appendChild (document.createTextNode (data.body));
             }
             catch (error) {
                script.text = data.body;
             }

    We append script to the body.

             document.body.appendChild (script);

    We invoke callback with null and data as its arguments, to indicate success. There's nothing else to do, so we close the ajax request and the function.

             callback (null, data);
          });
       }

    We define c.test, a function to execute a test suite on the browser. This function takes a single argument, tests.

       c.test = function (tests) {

    tests must be an array and each of its elements must also be an array.

          if (! c.prod && teishi.stop ('c.test', [
             ['tests', tests, 'array'],
             ['tests', tests, 'array', 'each'],

    We iterate each of the tests.

             dale.go (tests, function (test, k) {return [

    Each test must have a length of either two or three.

                ['test length', test.length, {min: 2, max: 3}, teishi.test.range],

    The first element of each test must be a string, which is the tag.

                ['test #' + (k + 1) + ' tag', test [0], 'string'],

    If the test has length 2, we expect its second element to be a function (the check function). If it has length 3, we expect its second element (the action function) and its third element (the check function) to be functions.

                test.length === 2 ? ['test #' + (k + 1) + ' check', test [1], 'function'] : [
                   ['test #' + (k + 1) + ' action', test [1], 'function'],
                   ['test #' + (k + 1) + ' check',  test [2], 'function']
                ]
             ]})

    If any of these checks fails, an error will be printed and c.test will return false.

          ], undefined, true)) return false;

    We define two variables: start, to mark the beginning time of the test suite; and runNext, a function that will run one test at a time. runNext takes an index k as its sole argument. We now proceed to define this function.

          var start = teishi.time (), runNext = function (k) {

    We define a local variable test and set it to the kth element of tests. This will be the test to be executed now.

             var test = tests [k];

    If there're no tests left, we print a success message which contains the total execution time for the entire test suite.

             if (! test) return clog ('c.test', 'All tests finished successfully (' + (teishi.time () - start) + ' ms)');

    We define a function check, which will be a wrapper around the check function specified in the last argument of the current test.

             var check = function () {

    We execute the check function (which will be the second or third element, depending on how many elements are contained by test) and store its result in a variable result. If result is false, this means the check has failed. We will throw an error, taking care of printing the tag of the test that failed.

                var result = test [test.length === 2 ? 1 : 2] ();
                if (result === false) throw new Error ('c.test: Test failed: ' + test [0]);

    Otherwise, we'll invoke runNext on the next index. We close check.

                runNext (k + 1);
             }

    We now print a message containing the tag for the current test.

             clog ('c.test', 'Running test:', test [0]);

    If there's no action function, we execute check directly and exit runNext.

             if (test.length === 2) return check ();

    If there's an action function, we invoke it passing to it another function as its first argument. This function is the next function, which will be optionally invoked by action to continue the chain of tests in case it performs an asynchronous operation.

             if (test [1] (function (wait) {

    If the first argument passed to next is undefined, next will invoke check and return.

                if (wait === undefined) return check ();

    If the first argument passed to next is not undefined, it must be a wait parameter, which will invoke check after a timeout. We validate that wait is an integer equal or larger than 0 and throw an error otherwise.

                if (type (wait) !== 'integer' || wait < 0) throw new Error ('c.test: wait parameter must zero or a positive integer but instead is ' + wait);

    We execute a timeout to execute check after wait milliseconds.

                setTimeout (check, wait);

    We close the invocation to action. If action returns anything except undefined, we invoke check. Otherwise, we'll let action invoke check on its own.

             }) !== undefined) check ();

    We close runNext.

          }

    We invoke runNext passing an index of 0 (to start at the first test) and close the function.

          runNext (0);
       }

    License

    cocholate is written by Federico Pereiro (fpereiro@gmail.com) and released into the public domain.

    Install

    npm i cocholate

    DownloadsWeekly Downloads

    1

    Version

    3.1.0

    License

    Public Domain

    Unpacked Size

    110 kB

    Total Files

    4

    Last publish

    Collaborators

    • fpereiro