Teaching Guide

Elvenware Logo

TOC

Overview

We further refine the midterm, getting ready for the final.

It's time to give our project a name. We'll call it the Hyperlink Explorer.

Copy your midterm into a new folder:

cp -r Week08-Midterm Week10-HyperExplorer

Open the project in WebStorm or your editor of choice.

Make the necessary changes regarding names.

Change the name of the class called elfMidterm to elfHyperExplorer. As a result of this change you will probably also need to make a change to your document.ready function.

Updates

Copy in the most recent tests from the templates directory:

cp $ELF_TEMPLATES/UnitTest/HyperExplorer/test*.js spec/.

Unless you have made your own modifications to these files, simply copy in the most recent mixins:

cp $ELF_TEMPLATES/JadeMixins/mixin*.jade views/.

If you did modify them, then do a diff or meld to update the files, being sure that the left and right iteration buttons have ID's of leftButton and rightButton.

Create a Spec Data Directory

Something like this:

mkdir spec/data
git mv spec/bitly-links.js spec/data/.

Copy your delicious-javascript-links.js file from the DelicousQuery project into spec/data.

Test your work

My callDelicious method looks like this:

callDelicious: function(subject) {
        'use strict';
        var feedUrl = 'http://feeds.delicious.com/v2/json/charliecalvert/' + subject;
        $.ajax({
            url: feedUrl,

            dataType: 'jsonp',

            success: function(data) {
                elfDelicious.deliciousLinks = data;
                $('#urlDelicious').empty();
                $.each(elfDelicious.deliciousLinks, function(index, deliciousLink) {
                    elfDelicious.appendUrl(index, deliciousLink);
                });
                $('#deliciousDetails').html(JSON.stringify(elfDelicious.deliciousLinks, null, 4));
            }
        });
    },

Copy in the delicious test suite:

cp $ELF_TEMPLATES/UnitTest/HyperExplorer/test-delicious.js spec/.

Make sure they all pass.

Delicious Utils

Move the following functions from DeliciousQuery to an object called elfDeliciousUtils:

Something like this:

var elfDeliciousUtils = {

    getAllUrls: function() {
        'use strict';
        // AND SO ON...
    },

    getMap: function() { ...

    // LOTS OF CODE OMITTED HERE
};

To see if you have construcuted it correctly, see the tests in charlie-test-delicious-analysis.js. To get the test to load, be sure something like this is in the file object in karma.conf.js

files: [
    // CODE OMITTED HERE
    'spec/test*.js',
    'spec/opt-test*.js',
    'spec/charlie-test*.js',
    // CODE OMITTED HERE
],

The logger

In elf-log.js

(function() {

    'use strict';

    function ElfLog() {
        this.debugLevel = this.logLevelWarn;
    }

    //var levels = ['error', 'warn', 'info', 'silent'];
    var that;

    ElfLog.prototype.logLevelError = 0;
    ElfLog.prototype.logLevelWarn = 1;
    ElfLog.prototype.logLevelDetails = 2;
    ElfLog.prototype.logLevelInfo = 3;
    ElfLog.prototype.logLevelSilent = 4;

    ElfLog.prototype.debugLevel = undefined;

    ElfLog.prototype.setLevel = function(level) {
        this.debugLevel = level;
    };

    ElfLog.prototype.log = function(level, message) {
        // console.log("Level:", level, 'debugLevel: ', this.debugLevel);
        if (level >= this.debugLevel) {
            if (typeof message !== 'string') {
                message = JSON.stringify(message);
            }
            console.log(level+': '+message);
        }
    };

    that = new ElfLog();
    window.elfLog = that;
})();

Use it like this:

elfLog.setLevel(elfLog.logLevelDetails); // Done once per application
elfLog.log(elfLog.logLevelDetails, 'ElfDisplay.Render index: ' + index);

Movement Tests

Make sure you have created spec/fixtures/bitly.html:

jade views/bitly.jade --out spec/fixtures/

Copy over test-movement and make sure all the tests pass:

cp $ELF_TEMPLATES/UnitTest/HyperExplorer/test-movement.js spec/.

Key Points:

The output we want:

Test Movement
    ✓ expects a button with an id of #leftButton
    ✓ expects a button with an id of #rightButton
    ✓ shows that elfMovement.left is called when selecting left button
    ✓ shows that elfMovement.right is called when selecting right button
    ✓ shows that elfDisplay.render is not called when selecting only left button
    ✓ shows that elfDisplay.render is called when selecting right then left button
    ✓ shows that elfDisplay.render is called when selecting right button
    ✓ shows that elfDisplay.showRecord is not called when selecting only left button
    ✓ shows that elfDisplay.showRecord is called when selecting right then left button
    ✓ shows that elfDisplay.showRecord is called when selecting right button
    ✓ shows that elfDisplay.showRecord gets valid data
    ✓ shows that movement.right called even if we click many times past end of array
    ✓ shows that the right boundary condition is checked
    ✓ shows that the right boundary condition gets valid data

Delicious Tests

Make sure you have created spec/fixtures/delicious.html:

jade views/delicious.jade --out spec/fixtures/

Make sure that there is no checkbox selected by default.

div.panel.panel-default
    div.panel-heading Select one or more CheckBoxes to search Delicious
    div.panel-body
        div
            input#chJavaScript(type='checkbox', name='deliciousCheckBox', value='JavaScript')
            label(for='chJavaScript') JavaScript
        div
            input#chBootstrap(type='checkbox', name='deliciousCheckBox', value='Bootstrap')
            label(for='chBootstrap')  Bootstrap
        div
            input#chNodeJs(type='checkbox', name='deliciousCheckBox', value='Node')
            label(for='chNodeJs')  NodeJs

Copy in text-delicious-fixtures.js:

cp $ELF_TEMPLATES/UnitTest/HyperExplorer/test-delicious-fixture.js spec/.

Note that if we are spying on a method that has an onclick event, we need to initialize the event after we have spied on the method:

it('expects selecting #chBootstrap to cause displayCheckBoxSelection to have been called', function() {
    var checkBox = document.getElementById('chBootstrap');
    spyOn(elfDelicious, 'displayCheckboxSelection');
    elfDelicious.deliciousSetup();
    $(checkBox).trigger('click');
    expect(elfDelicious.displayCheckboxSelection).toHaveBeenCalled();
});

Other Tests

Ignore these for now.

it('checks that elfBitly.getLinks calls elfDisplay.renderTable', function() {
    spyOn(elfDisplay, 'renderTable');
    elfBitly.getLinks(elfDownloads.dataTypes.dtBitly);
    expect(elfDisplay.renderTable).toHaveBeenCalled();
});

it('checks elfBitly.getLinks calls elfDisplay.render', function() {
    spyOn(elfDisplay, 'render');
    elfBitly.getLinks(elfDownloads.dataTypes.dtBitly);
    expect(elfDisplay.render).toHaveBeenCalled();
});

it('checks elfBitly.getLinks calls elfBitly.getUrl', function() {
    spyOn(elfBitly, 'getUrl');
    elfBitly.getLinks(elfDownloads.dataTypes.dtBitly);
    expect(elfBitly.getUrl).toHaveBeenCalled();
});

it('shows we can call getLinkHistoryArray', function() {
    elfBitly.getLinks(elfDownloads.dataTypes.dtBitly);
    var historyArray = elfBitly.getLinkHistoryArray();
    expect(historyArray.length).toBe(50);
});

it('shows getLinkHistoryItem sets elfBitly.linkIndex', function() {
    elfBitly.getLinks(elfDownloads.dataTypes.dtBitly);
    var historyArray = elfBitly.getLinkHistoryItem(12, true);
    expect(elfBitly.linkIndex).toBe(12);
});

Here are some results:

Test Delicous Fixture
    ✓ expects a checkbox with an id of #chJavaScript
    ✓ expects a checkbox with an id of #chJavaScript to be a checkbox
    ✓ expects a checkbox with an id of #chBootstrap
    ✓ expects a checkbox with an id of #chBootstrap to be a checkbox
    ✓ expects a checkbox with an id of #chNodeJs
    ✓ expects a checkbox with an id of #chNodeJs to be a checkbox
    ✓ expects selecting #chBootstrap to cause displayCheckBoxSelection to have been called
    ✓ expects selecting #chBootstrap to cause callDelicious to be called
    ✓ expects selecting #chBootstrap to cause callDelicious to be called with bootstrap
    ✓ expects selecting #chJavaScript to cause callDelicious to be called with javascript
    ✓ expects selecting #chNodeJs to cause callDelicious to be called with nodejs
    ✓ selecting #chNodeJs & #chJavaScript to cause callDelicious to be called with javascript+nodejs

Favicon

Be sure your favicon is set up correctly in app.js around line 18. The correct code will probably look like this:

app.use(favicon(path.join(__dirname, 'public', 'favicon.png')));

Tests Clean

The following should all come back clean and work:

There are some exceptions. In the spec/data directory you should have one or more files with names like bitly-links.js and delicious-links.js. In Gruntfile.js you can exclude these files from your jscs checks:

jscs: {
    src: ['**/*.js', '!spec/data/*.js'],
    options: {
        config: '.jscsrc'
    }
},