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 + ''; + if (actual != expected) { + output += ''; + output += ''; + } + output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    "; + + 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