diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..92a1f7fd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.bundle
+vendor/ruby
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 00000000..63214c9e
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gem "fssm", "0.2.0"
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 00000000..72aa2a29
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,10 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ fssm (0.2.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ fssm (= 0.2.0)
diff --git a/README.markdown b/README.markdown
index 3969b064..f38038d1 100644
--- a/README.markdown
+++ b/README.markdown
@@ -8,24 +8,32 @@ formatted dates and times embedded in your HTML (à la microformats).
First, load jQuery and the plugin:
-
-
+```html
+
+
+```
-Now, let's attach it to your timestamps on DOM ready:
+Now, let's attach it to your timestamps on DOM ready - put this in the head section:
-
- jQuery(document).ready(function() {
- jQuery("abbr.timeago").timeago();
- });
-
+```html
+
+```
This will turn all abbr elements with a class of timeago and an ISO 8601 timestamp in the title:
- July 17, 2008
+```html
+July 17, 2008
+```
into something like this:
- about 1 day ago
+```html
+about 1 day ago
+```
As time passes, the timestamps will automatically update.
diff --git a/Rakefile b/Rakefile
index 65a8d5f2..d56fdc5e 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,8 +1,8 @@
-verbose(true)
+require "bundler/setup"
-task :default => :test
+task :default => :watch
-desc 'Publish "marketing" docs'
+desc "Publish \"marketing\" docs"
task :publish do
sh("git rebase master gh-pages")
sh("git checkout master")
@@ -11,7 +11,36 @@ task :publish do
sh("git push --tags")
end
-desc 'Open your default browser with the test page'
+desc "Build everything"
+task :build do
+ rebuild_coffee
+end
+
+desc "Watch for changes and test the site"
+task :watch => :build do
+ sh("open test/index.html")
+ monitor
+end
+
+desc "Open your default browser with the test page"
task :test do
sh("open test/index.html")
end
+
+def rebuild_coffee(base = nil, relative = "**/*.coffee")
+ sh("coffee -c #{relative}")
+end
+
+def monitor
+ require "fssm"
+ puts ">>> Monitoring for changes. Press Ctrl-C to Stop."
+ FSSM.monitor do
+ path "." do
+ glob "**/*.coffee"
+ update &method(:rebuild_coffee)
+ delete &method(:rebuild_coffee)
+ create &method(:rebuild_coffee)
+ end
+ end
+end
+
diff --git a/index.html b/index.html
index 08c5ed77..e1fc3e73 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
timeago: a jQuery plugin
-
+
+
@@ -76,7 +76,6 @@ Other formats
Errors
Bad (letters): (this should be displayed).
- Bad (numbers): (this should be displayed).
Bad (blank): (this should be displayed).
Bad (missing): (this should be displayed).
@@ -131,6 +130,8 @@ Settings
[5840 days]
[23360 days]
+ [120 sec]
+
[-120 min]
[-60 sec]
[-30 sec]
@@ -231,6 +232,9 @@ Settings
loadYoungOldYears();
$("abbr.toyoungold").each(toWords);
+ loadNoSpaces();
+ $("abbr.nospaces").each(toWords);
+
loadPigLatin();
$("abbr.tolatin").each(toWords);
@@ -509,6 +513,10 @@ Settings
ok($("#testNumbersSettings2").html().match(/^nine minutes/), "Settings correctly applied");
ok($("#testNumbersSettings3").html().match(/^10 minutes/), "Settings correctly applied");
});
+
+ test("wordSeparator", function () {
+ ok($("#testNoSpaces1").html().match(/^2minutesago$/), "Settings correctly applied");
+ });
})(jQuery);
//]]>
diff --git a/test/qunit.css b/test/qunit.css
index e9404f59..87a5f820 100644
--- a/test/qunit.css
+++ b/test/qunit.css
@@ -20,10 +20,13 @@
#qunit-header {
padding: 0.5em 0 0.5em 1em;
-
- color: #fff;
- text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
+
+ color: #8699a4;
background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
@@ -33,7 +36,12 @@
#qunit-header a {
text-decoration: none;
- color: white;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
}
#qunit-banner {
@@ -83,6 +91,39 @@
-webkit-box-shadow: inset 0px 2px 13px #999;
}
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
/*** Test Counts */
#qunit-tests b.counts { color: black; }
diff --git a/test/qunit.js b/test/qunit.js
index 070f6b79..65663fcf 100644
--- a/test/qunit.js
+++ b/test/qunit.js
@@ -10,6 +10,10 @@
(function(window) {
+var defined = {
+ setTimeout: typeof window.setTimeout !== "undefined"
+}
+
var QUnit = {
// call on start of module test to prepend name to all tests
@@ -17,10 +21,11 @@ var QUnit = {
config.currentModule = name;
synchronize(function() {
- if ( config.currentModule ) {
+ if ( config.previousModule ) {
QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
}
+ config.previousModule = config.currentModule;
config.currentModule = name;
config.moduleTestEnvironment = testEnvironment;
config.moduleStats = { all: 0, bad: 0 };
@@ -84,7 +89,7 @@ var QUnit = {
var li = document.createElement("li");
li.appendChild( b );
li.id = "current-test-output";
- tests.appendChild( li )
+ tests.appendChild( li );
}
try {
@@ -107,7 +112,7 @@ var QUnit = {
callback.call(testEnvironment);
} catch(e) {
fail("Test " + name + " died, exception and test follows", e, callback);
- QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
+ QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
// else next test will carry the responsibility
saveGlobal();
@@ -128,16 +133,10 @@ var QUnit = {
});
synchronize(function() {
- try {
- QUnit.reset();
- } catch(e) {
- fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset);
- }
-
if ( config.expected && config.expected != config.assertions.length ) {
QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
}
-
+
var good = 0, bad = 0,
tests = id("qunit-tests");
@@ -152,7 +151,7 @@ var QUnit = {
var li = document.createElement("li");
li.className = assertion.result ? "pass" : "fail";
- li.innerHTML = assertion.message || "(no message)";
+ li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
ol.appendChild( li );
if ( assertion.result ) {
@@ -188,6 +187,7 @@ var QUnit = {
var li = id("current-test-output");
li.id = "";
li.className = bad ? "fail" : "pass";
+ li.style.display = resultDisplayStyle(!bad);
li.removeChild( li.firstChild );
li.appendChild( b );
li.appendChild( ol );
@@ -197,7 +197,6 @@ var QUnit = {
if ( toolbar ) {
toolbar.style.display = "block";
id("qunit-filter-pass").disabled = null;
- id("qunit-filter-missing").disabled = null;
}
}
@@ -211,11 +210,13 @@ var QUnit = {
}
}
- QUnit.testDone( testName, bad, config.assertions.length );
-
- if ( !window.setTimeout && !config.queue.length ) {
- done();
+ try {
+ QUnit.reset();
+ } catch(e) {
+ fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, QUnit.reset);
}
+
+ QUnit.testDone( testName, bad, config.assertions.length );
});
synchronize( done );
@@ -233,11 +234,15 @@ var QUnit = {
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function(a, msg) {
+ a = !!a;
+ var details = {
+ result: a,
+ message: msg
+ };
msg = escapeHtml(msg);
- QUnit.log(a, msg);
-
+ QUnit.log(a, msg, details);
config.assertions.push({
- result: !!a,
+ result: a,
message: msg
});
},
@@ -255,27 +260,27 @@ var QUnit = {
* @param String message (optional)
*/
equal: function(actual, expected, message) {
- push(expected == actual, actual, expected, message);
+ QUnit.push(expected == actual, actual, expected, message);
},
notEqual: function(actual, expected, message) {
- push(expected != actual, actual, expected, message);
+ QUnit.push(expected != actual, actual, expected, message);
},
deepEqual: function(actual, expected, message) {
- push(QUnit.equiv(actual, expected), actual, expected, message);
+ QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
},
notDeepEqual: function(actual, expected, message) {
- push(!QUnit.equiv(actual, expected), actual, expected, message);
+ QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
},
strictEqual: function(actual, expected, message) {
- push(expected === actual, actual, expected, message);
+ QUnit.push(expected === actual, actual, expected, message);
},
notStrictEqual: function(actual, expected, message) {
- push(expected !== actual, actual, expected, message);
+ QUnit.push(expected !== actual, actual, expected, message);
},
raises: function(fn, message) {
@@ -290,7 +295,7 @@ var QUnit = {
start: function() {
// A slight delay, to avoid any current callbacks
- if ( window.setTimeout ) {
+ if ( defined.setTimeout ) {
window.setTimeout(function() {
if ( config.timeout ) {
clearTimeout(config.timeout);
@@ -308,7 +313,7 @@ var QUnit = {
stop: function(timeout) {
config.blocking = true;
- if ( timeout && window.setTimeout ) {
+ if ( timeout && defined.setTimeout ) {
config.timeout = window.setTimeout(function() {
QUnit.ok( false, "Test timed out" );
QUnit.start();
@@ -403,10 +408,17 @@ extend(QUnit, {
/**
* Resets the test setup. Useful for tests that modify the DOM.
+ *
+ * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
*/
reset: function() {
if ( window.jQuery ) {
- jQuery("#main, #qunit-fixture").html( config.fixture );
+ jQuery( "#main, #qunit-fixture" ).html( config.fixture );
+ } else {
+ var main = id( 'main' ) || id( 'qunit-fixture' );
+ if ( main ) {
+ main.innerHTML = config.fixture;
+ }
}
},
@@ -469,6 +481,33 @@ extend(QUnit, {
return undefined;
},
+ push: function(result, actual, expected, message) {
+ var details = {
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeHtml(message) || (result ? "okay" : "failed");
+ message = '' + message + "";
+ expected = escapeHtml(QUnit.jsDump.parse(expected));
+ actual = escapeHtml(QUnit.jsDump.parse(actual));
+ var output = message + 'Expected: | ' + expected + ' |
';
+ if (actual != expected) {
+ output += 'Result: | ' + actual + ' |
';
+ output += 'Diff: | ' + QUnit.diff(expected, actual) +' |
';
+ }
+ output += "
";
+
+ QUnit.log(result, message, details);
+
+ config.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
// Logging callbacks
begin: function() {},
done: function(failures, total) {},
@@ -499,7 +538,16 @@ addEvent(window, "load", function() {
}
var banner = id("qunit-header");
if ( banner ) {
- banner.innerHTML = '' + banner.innerHTML + '';
+ var paramsIndex = location.href.lastIndexOf(location.search);
+ if ( paramsIndex > -1 ) {
+ var mainPageLocation = location.href.slice(0, paramsIndex);
+ if ( mainPageLocation == location.href ) {
+ banner.innerHTML = ' ' + banner.innerHTML + ' ';
+ } else {
+ var testName = decodeURIComponent(location.search.slice(1));
+ banner.innerHTML = '' + banner.innerHTML + ' › ' + testName + '';
+ }
+ }
}
var toolbar = id("qunit-testrunner-toolbar");
@@ -524,25 +572,6 @@ addEvent(window, "load", function() {
label.setAttribute("for", "qunit-filter-pass");
label.innerHTML = "Hide passed tests";
toolbar.appendChild( label );
-
- var missing = document.createElement("input");
- missing.type = "checkbox";
- missing.id = "qunit-filter-missing";
- missing.disabled = true;
- addEvent( missing, "click", function() {
- var li = document.getElementsByTagName("li");
- for ( var i = 0; i < li.length; i++ ) {
- if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
- li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
- }
- }
- });
- toolbar.appendChild( missing );
-
- label = document.createElement("label");
- label.setAttribute("for", "qunit-filter-missing");
- label.innerHTML = "Hide missing tests (untested code is broken code)";
- toolbar.appendChild( label );
}
var main = id('main') || id('qunit-fixture');
@@ -562,13 +591,15 @@ function done() {
}
if ( config.queue.length ) {
- config.doneTimer = window.setTimeout(function(){
- if ( !config.queue.length ) {
- done();
- } else {
- synchronize( done );
- }
- }, 13);
+ if ( defined.setTimeout ) {
+ config.doneTimer = window.setTimeout(function(){
+ if ( !config.queue.length ) {
+ done();
+ } else {
+ synchronize( done );
+ }
+ }, 13);
+ }
return;
}
@@ -634,8 +665,15 @@ function validTest( name ) {
return run;
}
+function resultDisplayStyle(passed) {
+ return passed && id("qunit-filter-pass") && id("qunit-filter-pass").checked ? 'none' : '';
+}
+
function escapeHtml(s) {
- s = s === null ? "" : s + "";
+ if (!s) {
+ return "";
+ }
+ s = s + "";
return s.replace(/[\&"<>\\]/g, function(s) {
switch(s) {
case "&": return "&";
@@ -648,24 +686,6 @@ function escapeHtml(s) {
});
}
-function push(result, actual, expected, message) {
- message = escapeHtml(message) || (result ? "okay" : "failed");
- message = '' + message + "";
- expected = escapeHtml(QUnit.jsDump.parse(expected));
- actual = escapeHtml(QUnit.jsDump.parse(actual));
- var output = message + ', expected: ' + expected + '';
- if (actual != expected) {
- output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual);
- }
-
- // can't use ok, as that would double-escape messages
- QUnit.log(result, output);
- config.assertions.push({
- result: !!result,
- message: output
- });
-}
-
function synchronize( callback ) {
config.queue.push( callback );
@@ -680,9 +700,8 @@ function process() {
while ( config.queue.length && !config.blocking ) {
if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
config.queue.shift()();
-
} else {
- setTimeout( process, 13 );
+ window.setTimeout( process, 13 );
break;
}
}
@@ -988,7 +1007,7 @@ QUnit.jsDump = (function() {
type = "date";
} else if (QUnit.is("Function", obj)) {
type = "function";
- } else if (obj.setInterval && obj.document && !obj.nodeType) {
+ } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
type = "window";
} else if (obj.nodeType === 9) {
type = "document";
@@ -1042,31 +1061,31 @@ QUnit.jsDump = (function() {
ret += ' ' + name;
ret += '(';
- ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
- return join( ret, this.parse(fn,'functionCode'), '}' );
+ ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
+ return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
},
array: array,
nodelist: array,
arguments: array,
object:function( map ) {
var ret = [ ];
- this.up();
+ QUnit.jsDump.up();
for ( var key in map )
- ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
- this.down();
+ ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
+ QUnit.jsDump.down();
return join( '{', ret, '}' );
},
node:function( node ) {
- var open = this.HTML ? '<' : '<',
- close = this.HTML ? '>' : '>';
+ var open = QUnit.jsDump.HTML ? '<' : '<',
+ close = QUnit.jsDump.HTML ? '>' : '>';
var tag = node.nodeName.toLowerCase(),
ret = open + tag;
- for ( var a in this.DOMAttrs ) {
- var val = node[this.DOMAttrs[a]];
+ for ( var a in QUnit.jsDump.DOMAttrs ) {
+ var val = node[QUnit.jsDump.DOMAttrs[a]];
if ( val )
- ret += ' ' + a + '=' + this.parse( val, 'attribute' );
+ ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
}
return ret + close + open + '/' + tag + close;
},
@@ -1094,8 +1113,8 @@ QUnit.jsDump = (function() {
'class':'className'
},
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
- indentChar:' ',//indentation unit
- multiline:false //if true, items in a collection, are separated by a \n, else just a space.
+ indentChar:' ',//indentation unit
+ multiline:true //if true, items in a collection, are separated by a \n, else just a space.
};
return jsDump;
@@ -1255,7 +1274,7 @@ QUnit.diff = (function() {
}
return str;
- }
+ };
})();
})(this);
diff --git a/test/qunit_setup.coffee b/test/qunit_setup.coffee
new file mode 100644
index 00000000..225f6dbb
--- /dev/null
+++ b/test/qunit_setup.coffee
@@ -0,0 +1,23 @@
+setupFn = -> null
+window.setup = (fn) ->
+ setupFn = fn
+window.moreSetup = (fn) ->
+ origSetup = setupFn
+ setup ->
+ origSetup.call(this)
+ fn.call(this)
+window.clearSetup = ->
+ setup -> null
+
+originalModule = window.module
+window.module = (description) ->
+ clearSetup()
+ originalModule(description)
+
+originalTest = window.test
+window.test = (description, testFn) ->
+ setupSnapshot = setupFn
+ originalTest description, ->
+ context = {}
+ setupSnapshot.call(context)
+ testFn.call(context)
diff --git a/test/qunit_setup.js b/test/qunit_setup.js
new file mode 100644
index 00000000..fce8b71b
--- /dev/null
+++ b/test/qunit_setup.js
@@ -0,0 +1,38 @@
+(function() {
+ var originalModule, originalTest, setupFn;
+ setupFn = function() {
+ return null;
+ };
+ window.setup = function(fn) {
+ return (setupFn = fn);
+ };
+ window.moreSetup = function(fn) {
+ var origSetup;
+ origSetup = setupFn;
+ return setup(function() {
+ origSetup.call(this);
+ return fn.call(this);
+ });
+ };
+ window.clearSetup = function() {
+ return setup(function() {
+ return null;
+ });
+ };
+ originalModule = window.module;
+ window.module = function(description) {
+ clearSetup();
+ return originalModule(description);
+ };
+ originalTest = window.test;
+ window.test = function(description, testFn) {
+ var setupSnapshot;
+ setupSnapshot = setupFn;
+ return originalTest(description, function() {
+ var context;
+ context = {};
+ setupSnapshot.call(context);
+ return testFn.call(context);
+ });
+ };
+}).call(this);
diff --git a/test/test_helpers.js b/test/test_helpers.js
index 6a345e2e..b8cf36a3 100644
--- a/test/test_helpers.js
+++ b/test/test_helpers.js
@@ -36,7 +36,8 @@ function loadPigLatin() {
month: "about-hay a-hay onth-may",
months: "%d onths-may",
year: "about-hay a-hay ear-yay",
- years: "%d years-yay"
+ years: "%d years-yay",
+ wordSeparator: " "
};
}
@@ -71,7 +72,8 @@ function loadRussian() {
month: "месяц",
months: function(value) { return numpf(value, "%d месяц", "%d месяца", "%d месяцев"); },
year: "год",
- years: function(value) { return numpf(value, "%d год", "%d года", "%d лет"); }
+ years: function(value) { return numpf(value, "%d год", "%d года", "%d лет"); },
+ wordSeparator: " "
};
})();
}
@@ -95,6 +97,13 @@ function loadMillis() {
};
}
+function loadNoSpaces() {
+ jQuery.extend(jQuery.timeago.settings.strings, {
+ minutes: "%dminutes",
+ wordSeparator: ""
+ });
+}
+
function loadYoungOldYears() {
jQuery.extend(jQuery.timeago.settings.strings, {
years: function(value) { return (value < 21) ? "%d young years" : "%d old years"; }
diff --git a/vendor/cache/fssm-0.2.0.gem b/vendor/cache/fssm-0.2.0.gem
new file mode 100644
index 00000000..13b47c28
Binary files /dev/null and b/vendor/cache/fssm-0.2.0.gem differ