I decided to touch up my HTML5 flashcards app. The app started out with me just playing around with HTML5 localStorage but as it was getting a bit more complex I decided to rewrite some parts in object oriented fashion. Also, I just saw on the Google Test Blog that the Google JS Test framework was open sourced so I decided to give it a dance.
Install gjstest – easy.
Wrote the Card and Deck class while writing simple test cases in parallel.
These are just snippets, view all the source at github.
/*
* card.js
* Defines a Card class to represent a flashcard
*/
// Card constructor
function Card(opts) {
if (opts == undefined) { opts = {} };
this.key = (opts.key) ? opts.key : makeKey();
//set attrs if key is in localStorage
if (localStorage[this.key]) {
var c = JSON.parse(localStorage[this.key]);
this.phrase1 = c.phrase1;
this.phrase2 = c.phrase2;
this.points = c.points;
return;
}
this.phrase1 = (opts.phrase1) ? opts.phrase1 : '';
this.phrase2 = (opts.phrase2) ? opts.phrase2 : '';
this.points = (opts.points) ? opts.points : 0;
}
/*
* deck.js
* Defines a Deck class to represent a deck of flashcards. A deck holds references to
* Card objects and viewing states
*/
//deck constructor
function Deck(key) {
this.key = key;
var d = (localStorage[key]) ? JSON.parse(localStorage[key]) : {} ;
//this.index = (d.index) ? d.index : 0;
this.index = 0;
//master_set contains all cards in order that they were added
this.master_set = (d.master_set) ? d.master_set : new Array();
//this.cards = (d.cards) ? d.cards : new Array();
//cards is the current ordering based on mutation options (random, low/high)
this.cards = this.master_set.slice();
this.mode_low = false;
this.mode_high = false;
this.mode_random = (d.mode_random) ? d.mode_random : false;
this.mode_reverse = (d.mode_reverse) ? d.mode_reverse : false;
this.processOptions();
}
Started off simple, just to sanity check my objects.
function CardTest() {}
registerTestSuite(CardTest);
CardTest.prototype.InitCard = function() {
var card = new Card();
//Assert new card fields are empty
expectNe('', card.key);
expectEq('', card.phrase1);
expectEq('',card.phrase2);
expectEq(0, card.points);
//Init with key
var card = new Card({'key':'abcd'});
expectEq('abcd', card.key);
expectEq('', card.phrase1);
expectEq('',card.phrase2);
expectEq(0, card.points);
}
function DeckTest() {}
registerTestSuite(DeckTest);
DeckTest.prototype.InitDeck = function() {
var deck = new Deck('new-deck');
expectEq(0, deck.length());
expectFalse(deck.mode_random);
expectFalse(deck.mode_reverse);
}
Then tried some more complex scenarios, try to break it, for example:
DeckTest.prototype.EdgeCases = function () {
//return current card of empty deck
var deck = new Deck('test-deck');
var card = deck.current();
expectEq(0, deck.length());
expectEq(null, card);
//delete from an empty deck
deck.deleteCard();
expectEq(0, deck.length());
}
Then fix and repeat.
The JS-test framework is lovely. One issue I came across was using the localStorage, although Chrome recognizes the localStorage mechanism, gjstest did not. Since localStorage has a very simple API I was able to fake it by defining a global and adding a removeItem method:
var localStorage = new Object();
localStorage.removeItem = function (key) {
delete this[key];
}
Not sure what I would do if I was using SQL storage, I might have to be more clever.