Skip to content

Commit

Permalink
Big refactoring + unit tests; allow setting the number of loans to show
Browse files Browse the repository at this point in the history
* Add UI for changing the number of loans to fetch
* Create a <style> element to put the map CSS
* Use that single, static element instead of creating a lot of anonymous
  <style> elements all the time
* Uppercase KivaMap and KivaData
* Make KivaMap receive two new parameters: a CSS selector for the map
  HTML and a CSS selector for the map CSS
* Big refactoring of KivaData: add 'dataSource' attribute, pointing to
  the source to get the loan data from; add 'numberLoans' attribute,
  with the number of loans to fetch; make the loan data loading much
  more flexible *and* testable; add 'eachLoan' iterator to avoid
  accessing 'loanInfo' directly from the outside
* Add two Javascript fixture files
* Add a lot of new unit tests
  • Loading branch information
emanchado committed Mar 29, 2009
1 parent c6c2468 commit 425a4a8
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 33 deletions.
1 change: 1 addition & 0 deletions test/fixtures/loans-simple.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/loans-two-pages.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

162 changes: 161 additions & 1 deletion test/unittest.html
Expand Up @@ -4,6 +4,8 @@
<head>
<script src="../widget/javascript/jquery.js"></script>
<script src="../widget/javascript/kivadata.js"></script>
<script src="fixtures/loans-simple.js"></script>
<script src="fixtures/loans-two-pages.js"></script>
<script src="../widget/javascript/map.js"></script>
<link rel="stylesheet" href="testsuite.css" type="text/css" media="screen" />

Expand All @@ -12,13 +14,171 @@

module("Latitude/longitude conversion");

test("a basic test example", function() {
test("Basic latLongToPixels tests", function() {
var x, y;
[x,y] = mapMaker.latLongToPixels(12.5833, -16.2719);
equals(x, 118, "The 'x' is correct");
equals(y, 250, "The 'y' is correct");
});

module("eachLoan iterator");

test("eachLoan simple test", function() {
var kivaData = new KivaData();
kivaData.loanInfo = loansSimple;
var loanList = [];
kivaData.eachLoan(function () {
loanList.push(this);
});
equals(loanList.length, 3,
"Should have iterated over 3 loans");
equals(loanList[0].name, 'Fatoumata Coly',
"Name of first loan should be correct");
equals(loanList[1].name, 'Marie Gomis',
"Name of second loan should be correct");
equals(loanList[2].name, 'Lydia Afriyie',
"Name of third loan should be correct");
});

module("Data fetching");

test("fetchLoanData function test", function() {
var kivaData = new KivaData();
kivaData.dataSource = function (n) { return "function value" };
var result = undefined;
kivaData.fetchLoanData(1, function (data) { result = data; });
equals(result, 'function value',
"fetchLoanData should call dataSource if it's a function");
});

test("fetchLoanData object test", function() {
var kivaData = new KivaData();
kivaData.dataSource = ['some', 'list'];
var result = undefined;
kivaData.fetchLoanData(1, function (data) { result = data; });
equals(result.length, 2, "The result list should have two elements");
equals(result[0], 'some', "The result's first element should be 'some'");
equals(result[1], 'list', "The result's second element should be 'list'");
});

test("refresh method", function() {
var kivaData = new KivaData();
kivaData.dataSource = ['some', 'list'];
var result = undefined;
kivaData.fetchLoanData(1, function (data) { result = data; });
equals(result.length, 2, "The result list should have two elements");
equals(result[0], 'some', "The result's first element should be 'some'");
equals(result[1], 'list', "The result's second element should be 'list'");
});

test("addLoans method", function() {
var kivaData = new KivaData();
equals(kivaData.loanInfo.loans.length, 0,
"There should be no loans initially");
kivaData.addLoans({'loans': [{'foo': 'bar'}, {'qux': 'flux'}]});
equals(kivaData.loanInfo.loans.length, 2,
"There should be 2 loans");

// Load at most one more loan
kivaData.addLoans({'loans': [{'third': 'and last'},
{'extra': 'should not load'}]},
1);
equals(kivaData.loanInfo.loans.length, 3,
"atMost parameter should be honoured");

// Less loans than atMost
kivaData.addLoans({'loans': [{'one': 'first'}, {'two': 'last'}]},
3);
equals(kivaData.loanInfo.loans.length, 5,
"Nothing strange should happen if there are less loans than atMost");
});

test("loadLoans infinite loop protection", function() {
expect(1);
var kivaData = new KivaData();
kivaData.numberLoans = 3;

// Dummy data source, never returns loans
kivaData.dataSource = {'loans': []}
kivaData.loadLoans(1, function () {
equals(kivaData.loanInfo.loans.length, 0,
"If there are no loans, it should not get into an infinite loop");
});
});

test("loadLoans pagination tests", function() {
expect(18);
var kivaData = new KivaData();
kivaData.numberLoans = 3;

// This data source will iterate over the pages in the loanPages variable
kivaData.dataSource = function (counter) {
return loanPages[(counter-1) % loanPages.length]
};
equals(kivaData.loanInfo.loans.length, 0,
"There should be no loans initially");
kivaData.loadLoans(1, function () {
equals(kivaData.loanInfo.loans.length, 3,
"There should be 3 loans");
equals(kivaData.loanInfo.loans[0].name, 'Fatoumata Coly',
"The name for the first loan should be correct");
equals(kivaData.loanInfo.loans[1].name, 'Marie Gomis',
"The name for the second loan should be correct");
equals(kivaData.loanInfo.loans[2].name, 'Lydia Afriyie',
"The name for the third loan should be correct");
});


// Again, now needing one loan from the second page
var kivaDataSecondPage = new KivaData();
kivaDataSecondPage.numberLoans = 4;

// This data source will iterate over the pages in the loanPages variable
kivaDataSecondPage.dataSource = function (counter) {
return loanPages[(counter-1) % loanPages.length]
};
equals(kivaDataSecondPage.loanInfo.loans.length, 0,
"There should be no loans initially");
kivaDataSecondPage.loadLoans(1, function () {
equals(kivaDataSecondPage.loanInfo.loans.length, 4,
"There should be 4 loans");
equals(kivaDataSecondPage.loanInfo.loans[0].name, 'Fatoumata Coly',
"The name for the first loan should be correct");
equals(kivaDataSecondPage.loanInfo.loans[1].name, 'Marie Gomis',
"The name for the second loan should be correct");
equals(kivaDataSecondPage.loanInfo.loans[2].name, 'Lydia Afriyie',
"The name for the third loan should be correct");
equals(kivaDataSecondPage.loanInfo.loans[3].name, 'Esther Adu',
"The name for the fourth loan should be correct");
});


// Again, now needing one loan from the second page
var kivaDataThreePages = new KivaData();
kivaDataThreePages.numberLoans = 8;

// This data source will iterate over the pages in the loanPages variable
kivaDataThreePages.dataSource = function (counter) {
return loanPages[(counter-1) % loanPages.length]
};
equals(kivaDataThreePages.loanInfo.loans.length, 0,
"There should be no loans initially");
kivaDataThreePages.loadLoans(1, function () {
equals(kivaDataThreePages.loanInfo.loans.length, 8,
"There should be 8 loans");
equals(kivaDataThreePages.loanInfo.loans[0].name, 'Fatoumata Coly',
"The name for the first loan should be correct");
equals(kivaDataThreePages.loanInfo.loans[1].name, 'Marie Gomis',
"The name for the second loan should be correct");
equals(kivaDataThreePages.loanInfo.loans[2].name, 'Lydia Afriyie',
"The name for the third loan should be correct");
equals(kivaDataThreePages.loanInfo.loans[3].name, 'Esther Adu',
"The name for the fourth loan should be correct");
equals(kivaDataThreePages.loanInfo.loans[7].name, 'Fatoumata Coly',
"The name for the eighth loan should be correct");
});
});

});
</script>

Expand Down
15 changes: 10 additions & 5 deletions widget/index.html
Expand Up @@ -9,8 +9,8 @@
<script type="text/javascript" src="javascript/kivamap.js"></script>

<script type="text/javascript">
var data = new kivaData();
var map = new kivaMap(data);
var data = new KivaData();
var map = new KivaMap('#main-map', '#map-css', data);

$(document).ready(function(ev) {
$('#spinner').show('fast');
Expand All @@ -27,7 +27,10 @@
window.close();
});

$('#numberloans').val(data.numberLoans);

$('#updatebutton').click(function () {
data.numberLoans = $('#numberloans').val();
$('#spinner').show('fast');
data.refresh(function () {
map.refresh();
Expand All @@ -50,6 +53,7 @@
}
}
</script>
<style type="text/css" id="map-css"></style>
</head>
<body>
<div id="container">
Expand All @@ -76,10 +80,11 @@ <h1>World Loanmeter</h1>
<div id="config">
<h2>Configuration</h2>

<p class="instructions">Click "Update" to update the data from
Kiva.org.</p>

<div style="padding-bottom: 10px">
<div>
Number of loans to show:
<input type="text" id="numberloans" name="numberloans" size="3" maxlength="3" value="" />
</div>
<button id="updatebutton" type="button">Update</button>
</div>
</div>
Expand Down
80 changes: 70 additions & 10 deletions widget/javascript/kivadata.js
@@ -1,4 +1,5 @@
function kivaData () {
function KivaData () {
this.dataSource = 'http://api.kivaws.org/v1/loans/newest.json';
// See http://build.kiva.org/docs/data/loans
this.linkTemplate = 'http://kiva.org/app.php?page=businesses&action=about&id=LOANID';
// See http://build.kiva.org/docs/data/media
Expand All @@ -7,20 +8,79 @@ function kivaData () {
"pattern": "http:\/\/www.kiva.org\/img\/<size>\/<id>.jpg"}
];
// Cached copy of the loan information
this.loanInfo = undefined;
this.loanInfo = {'loans': []};
// Number of loans to fetch
this.numberLoans = 20;

// Fetches the loan data from the data source. If dataSource is a string
// (the default unless overriden), it's interpreted as a URL and an Ajax
// call is made to fetch the data. If it's a function, the function is
// called without parameters. Otherwise, its value is taken directly as the
// value to fetch.
// Parameters:
// n - Number of times the function has been called, including current.
// f - Callback function. It will be called with the received data.
this.fetchLoanData = function(n, f) {
if (typeof(this.dataSource) == 'string') {
finalUrl = this.dataSource + "?page=" + n
opera.postError('Requesting from ' + finalUrl);
jQuery.getJSON(finalUrl, {}, f);
} else if (typeof(this.dataSource) == 'function') {
f(this.dataSource(n));
} else {
f(this.dataSource);
}
};

// Adds the given loans to the current list of loans. If the second
// parameter, atMost, is given, no more than that number of loans are
// loaded
this.addLoans = function(data, atMost) {
var self = this;
if (!atMost) { atMost = 9999; }
jQuery.each(data.loans, function () {
if (atMost-- > 0) {
self.loanInfo.loans.push(this);
}
});
};

// Loads loans from the data source until there is no more data, or there
// are enough (numberLoans property)
this.loadLoans = function(n, f) {
var self = this;
var initialLoans = self.loanInfo.loans.length;
if (initialLoans < self.numberLoans) {
self.fetchLoanData(n, function (data) {
self.addLoans(data,
self.numberLoans -
initialLoans);
// If we didn't get any data stop now
if (self.loanInfo.loans.length >
initialLoans) {
self.loadLoans(n+1, f);
}
else if (f) {
f();
}
});
} else {
if (f) {
f();
}
}
};

// Refresh the copy of the loan information. Takes an optional function,
// that will be executed if given
this.refresh = function (f) {
var self = this;
jQuery.getJSON('http://api.kivaws.org/v1/loans/newest.json',
{},
function (data) {
self.loanInfo = data;
if (f) {
f();
}
});
this.loanInfo = {'loans': []};
this.loadLoans(1, f);
};

this.eachLoan = function(f) {
jQuery.each(this.loanInfo.loans, f);
};

this._findImageTemplate = function (templateId) {
Expand Down
18 changes: 11 additions & 7 deletions widget/javascript/kivamap.js
@@ -1,10 +1,12 @@
function kivaMap (data) {
this.data = data;
this.loansInPlace = {};
function KivaMap (mapSelector, mapCssSelector, data) {
this.mapSelector = mapSelector;
this.mapCssSelector = mapCssSelector;
this.data = data;
this.loansInPlace = {};

this.placeLoans = function () {
var self = this;
jQuery.each(this.data.loanInfo.loans, function () {
this.data.eachLoan(function () {
var loan = this;

// Collect all the loans in each place, and put only one dot in the
Expand All @@ -14,14 +16,15 @@ function kivaMap (data) {
if (self.loansInPlace[placeId] == undefined) { // First loan
[lat, lon] = loan.location.geo.pairs.split(' ');

mapMaker.placeIdByLatitudeAndLongitude('#'+placeId,
mapMaker.placeIdByLatitudeAndLongitude(self.mapCssSelector,
'#'+placeId,
parseFloat(lat),
parseFloat(lon));
htmlString = '<a class="location" href="#" ' +
'id="' + placeId +
'">' + placeId + '</a>';

$('#main-map').prepend(htmlString);
$(self.mapSelector).prepend(htmlString);
var f = function () { map.showInfoInPanel(placeId); return false };
$('#'+placeId).mouseover(f).focus(f);

Expand Down Expand Up @@ -53,7 +56,8 @@ function kivaMap (data) {

this.refresh = function () {
this.loansInPlace = {};
$('#main-map').html('');
$(this.mapSelector).html('');
$(this.mapCssSelector).html('');
this.placeLoans();
};

Expand Down

0 comments on commit 425a4a8

Please sign in to comment.