Added JavaScript Specs and Client-side Templates
- New rake task `rake jst:compile` to precompile JavaScript templates into functions. Add handlebars templates to app/views/jst and they'll get compiled to public/javascripts/jst - New rake task `rake jasmine` and `jasmine:ci` to run JavaScript specs. Add specs to spec/coffeescripts and they'll get compiled into spec/javascripts - Added Guard gem `$ guard` that watches coffeescript and handlebars files and compiles them when changes are made. - Created Handlebars Ruby class that precompiles the templates into JavaScript functions - Added JS Template constructor to abstract our tempting API Change-Id: Ie993d0fc50d49b161ed94dbc066c4475cefdc427 Reviewed-on: https://gerrit.instructure.com/5813 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Ryan Shaw <ryan@instructure.com>
This commit is contained in:
parent
e85e98cb25
commit
0f9d86d0d8
|
@ -50,4 +50,4 @@ public/assets/*
|
|||
doc/api/*
|
||||
doc/plugins/*
|
||||
public/doc/api/*
|
||||
.yardoc/*
|
||||
.yardoc/*
|
5
Gemfile
5
Gemfile
|
@ -70,6 +70,7 @@ group :test do
|
|||
gem 'selenium-webdriver', '2.5.0'
|
||||
gem 'webrat', '0.7.2'
|
||||
gem 'yard', '0.7.2'
|
||||
gem 'jasmine', '1.1.0'
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
@ -77,12 +78,16 @@ group :development do
|
|||
gem 'ruby_parser', '2.0.6'
|
||||
gem 'sexp_processor', '3.0.5'
|
||||
gem 'ya2yaml', '0.30'
|
||||
gem 'guard', '0.7.0'
|
||||
gem 'guard-coffeescript', '0.1.0'
|
||||
gem 'execjs', '1.2.8'
|
||||
end
|
||||
|
||||
group :redis do
|
||||
gem 'redis-store', '1.0.0.rc1'
|
||||
end
|
||||
|
||||
|
||||
# The closure-compiler gem has an undocumented
|
||||
# gem dependency on windows with ruby < 1.9. I'm
|
||||
# working to get this fixed in the gem itself, but
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
guard 'coffeescript', :output => 'public/javascripts/compiled' do
|
||||
watch('^app/coffeescripts/(.*)\.coffee')
|
||||
end
|
||||
|
||||
guard 'coffeescript', :output => 'spec/javascripts' do
|
||||
watch('^spec/coffeescripts/(.*)\.coffee')
|
||||
end
|
||||
|
||||
guard 'jst', :output => 'public/javascripts/jst' do
|
||||
watch('app/views/jst/(.*)\.handlebars')
|
||||
end
|
||||
|
||||
#guard 'livereload' do
|
||||
# watch('^spec/javascripts/.+\.js$')
|
||||
# watch('^public/javascripts/compiled/.+\.js$')
|
||||
#end
|
|
@ -0,0 +1,46 @@
|
|||
# A client-side templating wrapper. Templates are compiled with the rake task
|
||||
# `$ rake jst:compile` or automatically using the guard gem `$ guard`.
|
||||
# Don't call the templating object directly (like Handlebars), use this class.
|
||||
class @Template
|
||||
|
||||
# If called w/o `new`, it will return the HTML string immediately.
|
||||
# i.e. `Template(test, {foo: 'bar'})` => '<div>bar</div>'
|
||||
#
|
||||
# If called with `new` it will return an instance.
|
||||
#
|
||||
# Arguments:
|
||||
# @name (string):
|
||||
# The id of template. Examples [template path] => [id]:
|
||||
# `app/views/jst/foo.handlebars` becomes `foo`
|
||||
# `app/views/jst/foo/bar/baz.handlebars` becomes `foo/bar/baz`
|
||||
#
|
||||
# @locals (object: optional):
|
||||
# Object literal of key:value pairs for use as local vars in the template.
|
||||
#
|
||||
constructor: (@name, @locals) ->
|
||||
if this instanceof Template isnt true
|
||||
return new Template(name, locals).toHTML()
|
||||
|
||||
# Generates an HTML string from the template.
|
||||
#
|
||||
# Arguments:
|
||||
# locals (object: optional) - locals to use in the template, if omitted the
|
||||
# instance locals property will be used.
|
||||
#
|
||||
# Returns:
|
||||
# String - and HTML string
|
||||
toHTML: (locals = @locals) ->
|
||||
Handlebars.templates[@name](locals)
|
||||
|
||||
# Creates an element rendered with the template.
|
||||
#
|
||||
# Arguments:
|
||||
# locals (object: optional):
|
||||
# locals to use in the template, if omitted the instance locals property
|
||||
# will be used.
|
||||
#
|
||||
# Returns:
|
||||
# jQuery Element Collection
|
||||
toElement: (locals) ->
|
||||
html = @toHTML locals
|
||||
jQuery('<div/>').html(html)
|
|
@ -10,6 +10,7 @@ javascripts:
|
|||
- public/javascripts/firebugx.js
|
||||
- public/javascripts/datejs_to_iso_string_patch.js
|
||||
- public/javascripts/date.js
|
||||
- public/javascripts/handlebars.vm.js
|
||||
- public/javascripts/jquery-1.5.2.js
|
||||
- public/javascripts/jquery-ui-1.8.js
|
||||
- public/javascripts/i18n.js
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
require 'guard'
|
||||
require 'guard/guard'
|
||||
require 'lib/handlebars/handlebars'
|
||||
|
||||
module Guard
|
||||
class JST < Guard
|
||||
# Compiles templates from app/views/jst to public/javascripts/jst
|
||||
def run_on_change(paths)
|
||||
paths.each do |path|
|
||||
puts "Running #{path}"
|
||||
Handlebars.compile_file path, 'app/views/jst', @options[:output]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,54 @@
|
|||
require 'fileutils'
|
||||
|
||||
# Precompiles handlebars templates into JavaScript function strings
|
||||
class Handlebars
|
||||
|
||||
@@header = '!function() { var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};'
|
||||
@@footer = '}()'
|
||||
|
||||
class << self
|
||||
|
||||
# Recursively compiles a source directory of .handlebars templates into a
|
||||
# destination directory. Immitates the node.js bin script at
|
||||
# https://github.com/wycats/handlebars.js/blob/master/bin/handlebars
|
||||
#
|
||||
# Arguments:
|
||||
# root_path (string) - The root directory to find templates to compile
|
||||
# compiled_path (string) - The destination directory in which to save the
|
||||
# compiled templates.
|
||||
def compile(root_path, compiled_path)
|
||||
files = Dir["#{root_path}/**/**.handlebars"]
|
||||
files.each { |file| compile_file file, root_path, compiled_path }
|
||||
end
|
||||
|
||||
# Compiles a single file into a destination directory.
|
||||
#
|
||||
# Arguments:
|
||||
# file (string) - The file to compile.
|
||||
# root_path - See `compile`
|
||||
# compiled_path - See `compile`
|
||||
def compile_file(file, root_path, compiled_path)
|
||||
require 'execjs'
|
||||
id = file.gsub(root_path, '').gsub(/.handlebars$/, '')
|
||||
path = "#{compiled_path}/#{id}.js"
|
||||
dir = File.dirname(path)
|
||||
template = context.call "Handlebars.precompile", File.read(file)
|
||||
js = "#{@@header}\ntemplates['#{id}'] = template(#{template}); #{@@footer}"
|
||||
FileUtils.mkdir_p(dir) unless File.exists?(dir)
|
||||
File.open(path, 'w') { |file| file.write(js) }
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Returns the JavaScript context
|
||||
def context
|
||||
@context ||= self.set_context
|
||||
end
|
||||
|
||||
# Compiles and caches the handlebars JavaScript
|
||||
def set_context
|
||||
handlebars_source = File.read(File.dirname(__FILE__) + '/vendor/handlebars.js')
|
||||
@context = ExecJS.compile handlebars_source
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
|||
begin
|
||||
require 'jasmine'
|
||||
load 'jasmine/tasks/jasmine.rake'
|
||||
rescue LoadError
|
||||
task :jasmine do
|
||||
abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
require 'lib/handlebars/handlebars'
|
||||
|
||||
namespace :jst do
|
||||
desc 'precompile handlebars templates from app/views/jst to public/javascripts/jst'
|
||||
task :compile do
|
||||
Handlebars.compile 'app/views/jst', 'public/javascripts/jst'
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
(function() {
|
||||
this.Template = (function() {
|
||||
function Template(name, locals) {
|
||||
this.name = name;
|
||||
this.locals = locals;
|
||||
if (this instanceof Template !== true) {
|
||||
return new Template(name, locals).toHTML();
|
||||
}
|
||||
}
|
||||
Template.prototype.toHTML = function(locals) {
|
||||
if (locals == null) {
|
||||
locals = this.locals;
|
||||
}
|
||||
return Handlebars.templates[this.name](locals);
|
||||
};
|
||||
Template.prototype.toElement = function(locals) {
|
||||
var html;
|
||||
html = this.toHTML(locals);
|
||||
return jQuery('<div/>').html(html);
|
||||
};
|
||||
return Template;
|
||||
})();
|
||||
}).call(this);
|
|
@ -0,0 +1,212 @@
|
|||
// lib/handlebars/base.js
|
||||
var Handlebars = {};
|
||||
|
||||
Handlebars.VERSION = "1.0.beta.2";
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if(inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
this.partials[name] = str;
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Could not find property '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
|
||||
var ret = "";
|
||||
var type = Object.prototype.toString.call(context);
|
||||
|
||||
if(type === "[object Function]") {
|
||||
context = context();
|
||||
}
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var ret = "";
|
||||
|
||||
if(context && context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(context, options) {
|
||||
if(!context || Handlebars.Utils.isEmpty(context)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
options.fn = inverse;
|
||||
options.inverse = fn;
|
||||
|
||||
return Handlebars.helpers['if'].call(this, context, options);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
return options.fn(context);
|
||||
});
|
||||
;
|
||||
// lib/handlebars/utils.js
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
for (var p in tmp) {
|
||||
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error;
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
(function() {
|
||||
var escape = {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /&(?!\w+;)|[<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return true;
|
||||
} else if (value === null) {
|
||||
return true;
|
||||
} else if (value === false) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();;
|
||||
// lib/handlebars/vm.js
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
return Handlebars.VM.program(fn, data);
|
||||
} else if(programWrapper) {
|
||||
return programWrapper;
|
||||
} else {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
|
||||
return programWrapper;
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(fn, data, $depth) {
|
||||
var args = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
},
|
||||
program: function(fn, data) {
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials) {
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, {helpers: helpers, partials: partials});
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in vm mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial);
|
||||
return partials[name](context, {helpers: helpers, partials: partials});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
;
|
|
@ -0,0 +1,15 @@
|
|||
!function() { var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['/CustomList/content'] = template(function (Handlebars,depth0,helpers,partials,data) {
|
||||
helpers = helpers || Handlebars.helpers;
|
||||
var buffer = "", stack1, self=this, functionType="function", helperMissing=helpers.helperMissing, undef=void 0, escapeExpression=this.escapeExpression;
|
||||
|
||||
|
||||
stack1 = helpers.howdy || depth0.howdy;
|
||||
if(typeof stack1 === functionType) { stack1 = stack1.call(depth0, { hash: {} }); }
|
||||
else if(stack1=== undef) { stack1 = helperMissing.call(depth0, "howdy", { hash: {} }); }
|
||||
buffer += escapeExpression(stack1) + " ";
|
||||
stack1 = helpers.dowdy || depth0.dowdy;
|
||||
if(typeof stack1 === functionType) { stack1 = stack1.call(depth0, { hash: {} }); }
|
||||
else if(stack1=== undef) { stack1 = helperMissing.call(depth0, "dowdy", { hash: {} }); }
|
||||
buffer += escapeExpression(stack1) + " fasdfasfd \n";
|
||||
return buffer;}); }()
|
|
@ -0,0 +1,17 @@
|
|||
describe "Template", ->
|
||||
|
||||
it 'should create an HTML string from a template', ->
|
||||
# templates names derived from path in app/views/jst/
|
||||
# here the file app/views/jst/test.handlebars becomes 'test'
|
||||
template = new Template 'test'
|
||||
html = template.toHTML(foo: 'bar')
|
||||
expect(html).toEqual('bar')
|
||||
|
||||
it 'should create a collection of DOM elements', ->
|
||||
template = new Template 'test'
|
||||
element = template.toElement(foo: 'bar')
|
||||
expect(element.html()).toEqual('bar')
|
||||
|
||||
it 'should return the HTML string when called w/o new', ->
|
||||
html = Template('test', {foo: 'bar'})
|
||||
expect(html).toEqual('bar')
|
|
@ -0,0 +1,27 @@
|
|||
(function() {
|
||||
describe("Template", function() {
|
||||
it('should create an HTML string from a template', function() {
|
||||
var html, template;
|
||||
template = new Template('test');
|
||||
html = template.toHTML({
|
||||
foo: 'bar'
|
||||
});
|
||||
return expect(html).toEqual('bar');
|
||||
});
|
||||
it('should create a collection of DOM elements', function() {
|
||||
var element, template;
|
||||
template = new Template('test');
|
||||
element = template.toElement({
|
||||
foo: 'bar'
|
||||
});
|
||||
return expect(element.html()).toEqual('bar');
|
||||
});
|
||||
return it('should return the HTML string when called w/o new', function() {
|
||||
var html;
|
||||
html = Template('test', {
|
||||
foo: 'bar'
|
||||
});
|
||||
return expect(html).toEqual('bar');
|
||||
});
|
||||
});
|
||||
}).call(this);
|
|
@ -0,0 +1 @@
|
|||
<div id="fixture"></div>
|
|
@ -0,0 +1,28 @@
|
|||
// A precompiled Handlebars template
|
||||
(function() {
|
||||
var template = Handlebars.template,
|
||||
templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['test'] = template(function(Handlebars, depth0, helpers, partials, data) {
|
||||
helpers = helpers || Handlebars.helpers;
|
||||
var stack1,
|
||||
self = this,
|
||||
functionType = "function",
|
||||
helperMissing = helpers.helperMissing,
|
||||
undef = void 0,
|
||||
escapeExpression = this.escapeExpression;
|
||||
|
||||
|
||||
stack1 = helpers.foo || depth0.foo;
|
||||
if (typeof stack1 === functionType) {
|
||||
stack1 = stack1.call(depth0, {
|
||||
hash: {}
|
||||
});
|
||||
}
|
||||
else if (stack1 === undef) {
|
||||
stack1 = helperMissing.call(depth0, "foo", {
|
||||
hash: {}
|
||||
});
|
||||
}
|
||||
return escapeExpression(stack1);
|
||||
});
|
||||
})()
|
|
@ -0,0 +1,254 @@
|
|||
var readFixtures = function() {
|
||||
return jasmine.getFixtures().proxyCallTo_('read', arguments);
|
||||
};
|
||||
|
||||
var loadFixtures = function() {
|
||||
jasmine.getFixtures().proxyCallTo_('load', arguments);
|
||||
};
|
||||
|
||||
var setFixtures = function(html) {
|
||||
jasmine.getFixtures().set(html);
|
||||
};
|
||||
|
||||
var sandbox = function(attributes) {
|
||||
return jasmine.getFixtures().sandbox(attributes);
|
||||
};
|
||||
|
||||
var spyOnEvent = function(selector, eventName) {
|
||||
jasmine.JQuery.events.spyOn(selector, eventName);
|
||||
}
|
||||
|
||||
jasmine.getFixtures = function() {
|
||||
return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
|
||||
};
|
||||
|
||||
jasmine.Fixtures = function() {
|
||||
this.containerId = 'jasmine-fixtures';
|
||||
this.fixturesCache_ = {};
|
||||
this.fixturesPath = 'spec/javascripts/fixtures';
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.set = function(html) {
|
||||
this.cleanUp();
|
||||
this.createContainer_(html);
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.load = function() {
|
||||
this.cleanUp();
|
||||
this.createContainer_(this.read.apply(this, arguments));
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.read = function() {
|
||||
var htmlChunks = [];
|
||||
|
||||
var fixtureUrls = arguments;
|
||||
for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
|
||||
htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
|
||||
}
|
||||
|
||||
return htmlChunks.join('');
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.clearCache = function() {
|
||||
this.fixturesCache_ = {};
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.cleanUp = function() {
|
||||
$('#' + this.containerId).remove();
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.sandbox = function(attributes) {
|
||||
var attributesToSet = attributes || {};
|
||||
return $('<div id="sandbox" />').attr(attributesToSet);
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.createContainer_ = function(html) {
|
||||
var container = $('<div id="' + this.containerId + '" />');
|
||||
container.html(html);
|
||||
$('body').append(container);
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
|
||||
if (typeof this.fixturesCache_[url] == 'undefined') {
|
||||
this.loadFixtureIntoCache_(url);
|
||||
}
|
||||
return this.fixturesCache_[url];
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
|
||||
var self = this;
|
||||
var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
|
||||
$.ajax({
|
||||
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
|
||||
cache: false,
|
||||
dataType: 'html',
|
||||
url: url,
|
||||
success: function(data) {
|
||||
self.fixturesCache_[relativeUrl] = data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
|
||||
return this[methodName].apply(this, passedArguments);
|
||||
};
|
||||
|
||||
|
||||
jasmine.JQuery = function() {};
|
||||
|
||||
jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
|
||||
return $('<div/>').append(html).html();
|
||||
};
|
||||
|
||||
jasmine.JQuery.elementToString = function(element) {
|
||||
return $('<div />').append(element.clone()).html();
|
||||
};
|
||||
|
||||
jasmine.JQuery.matchersClass = {};
|
||||
|
||||
(function(namespace) {
|
||||
var data = {
|
||||
spiedEvents: {},
|
||||
handlers: []
|
||||
};
|
||||
|
||||
namespace.events = {
|
||||
spyOn: function(selector, eventName) {
|
||||
var handler = function(e) {
|
||||
data.spiedEvents[[selector, eventName]] = e;
|
||||
};
|
||||
$(selector).bind(eventName, handler);
|
||||
data.handlers.push(handler);
|
||||
},
|
||||
|
||||
wasTriggered: function(selector, eventName) {
|
||||
return !!(data.spiedEvents[[selector, eventName]]);
|
||||
},
|
||||
|
||||
cleanUp: function() {
|
||||
data.spiedEvents = {};
|
||||
data.handlers = [];
|
||||
}
|
||||
}
|
||||
})(jasmine.JQuery);
|
||||
|
||||
(function(){
|
||||
var jQueryMatchers = {
|
||||
toHaveClass: function(className) {
|
||||
return this.actual.hasClass(className);
|
||||
},
|
||||
|
||||
toBeVisible: function() {
|
||||
return this.actual.is(':visible');
|
||||
},
|
||||
|
||||
toBeHidden: function() {
|
||||
return this.actual.is(':hidden');
|
||||
},
|
||||
|
||||
toBeSelected: function() {
|
||||
return this.actual.is(':selected');
|
||||
},
|
||||
|
||||
toBeChecked: function() {
|
||||
return this.actual.is(':checked');
|
||||
},
|
||||
|
||||
toBeEmpty: function() {
|
||||
return this.actual.is(':empty');
|
||||
},
|
||||
|
||||
toExist: function() {
|
||||
return this.actual.size() > 0;
|
||||
},
|
||||
|
||||
toHaveAttr: function(attributeName, expectedAttributeValue) {
|
||||
return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
|
||||
},
|
||||
|
||||
toHaveId: function(id) {
|
||||
return this.actual.attr('id') == id;
|
||||
},
|
||||
|
||||
toHaveHtml: function(html) {
|
||||
return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
|
||||
},
|
||||
|
||||
toHaveText: function(text) {
|
||||
if (text && jQuery.isFunction(text.test)) {
|
||||
return text.test(this.actual.text());
|
||||
} else {
|
||||
return this.actual.text() == text;
|
||||
}
|
||||
},
|
||||
|
||||
toHaveValue: function(value) {
|
||||
return this.actual.val() == value;
|
||||
},
|
||||
|
||||
toHaveData: function(key, expectedValue) {
|
||||
return hasProperty(this.actual.data(key), expectedValue);
|
||||
},
|
||||
|
||||
toBe: function(selector) {
|
||||
return this.actual.is(selector);
|
||||
},
|
||||
|
||||
toContain: function(selector) {
|
||||
return this.actual.find(selector).size() > 0;
|
||||
},
|
||||
|
||||
toBeDisabled: function(selector){
|
||||
return this.actual.attr("disabled") == true;
|
||||
}
|
||||
};
|
||||
|
||||
var hasProperty = function(actualValue, expectedValue) {
|
||||
if (expectedValue === undefined) {
|
||||
return actualValue !== undefined;
|
||||
}
|
||||
return actualValue == expectedValue;
|
||||
};
|
||||
|
||||
var bindMatcher = function(methodName) {
|
||||
var builtInMatcher = jasmine.Matchers.prototype[methodName];
|
||||
|
||||
jasmine.JQuery.matchersClass[methodName] = function() {
|
||||
if (this.actual instanceof jQuery) {
|
||||
var result = jQueryMatchers[methodName].apply(this, arguments);
|
||||
this.actual = jasmine.JQuery.elementToString(this.actual);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (builtInMatcher) {
|
||||
return builtInMatcher.apply(this, arguments);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
for(var methodName in jQueryMatchers) {
|
||||
bindMatcher(methodName);
|
||||
}
|
||||
})();
|
||||
|
||||
beforeEach(function() {
|
||||
this.addMatchers(jasmine.JQuery.matchersClass);
|
||||
this.addMatchers({
|
||||
toHaveBeenTriggeredOn: function(selector) {
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected event " + this.actual + " to have been triggered on" + selector,
|
||||
"Expected event " + this.actual + " not to have been triggered on" + selector
|
||||
];
|
||||
};
|
||||
return jasmine.JQuery.events.wasTriggered(selector, this.actual);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.getFixtures().cleanUp();
|
||||
jasmine.JQuery.events.cleanUp();
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
# src_files
|
||||
#
|
||||
# Return an array of filepaths relative to src_dir to include before jasmine specs.
|
||||
# Default: []
|
||||
#
|
||||
# EXAMPLE:
|
||||
#
|
||||
# src_files:
|
||||
# - lib/source1.js
|
||||
# - lib/source2.js
|
||||
# - dist/**/*.js
|
||||
#
|
||||
src_files:
|
||||
- public/javascripts/**/*.js
|
||||
|
||||
# stylesheets
|
||||
#
|
||||
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
|
||||
# Default: []
|
||||
#
|
||||
# EXAMPLE:
|
||||
#
|
||||
# stylesheets:
|
||||
# - css/style.css
|
||||
# - stylesheets/*.css
|
||||
#
|
||||
stylesheets:
|
||||
- stylesheets/**/*.css
|
||||
|
||||
# helpers
|
||||
#
|
||||
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
|
||||
# Default: ["helpers/**/*.js"]
|
||||
#
|
||||
# EXAMPLE:
|
||||
#
|
||||
# helpers:
|
||||
# - helpers/**/*.js
|
||||
#
|
||||
helpers:
|
||||
- helpers/**/*.js
|
||||
|
||||
# spec_files
|
||||
#
|
||||
# Return an array of filepaths relative to spec_dir to include.
|
||||
# Default: ["**/*[sS]pec.js"]
|
||||
#
|
||||
# EXAMPLE:
|
||||
#
|
||||
# spec_files:
|
||||
# - **/*[sS]pec.js
|
||||
#
|
||||
spec_files:
|
||||
- '**/*[sS]pec.js'
|
||||
|
||||
# src_dir
|
||||
#
|
||||
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
|
||||
# Default: project root
|
||||
#
|
||||
# EXAMPLE:
|
||||
#
|
||||
# src_dir: public
|
||||
#
|
||||
src_dir:
|
||||
|
||||
# spec_dir
|
||||
#
|
||||
# Spec directory path. Your spec_files must be returned relative to this path.
|
||||
# Default: spec/javascripts
|
||||
#
|
||||
# EXAMPLE:
|
||||
#
|
||||
# spec_dir: spec/javascripts
|
||||
#
|
||||
spec_dir: spec/javascripts
|
|
@ -0,0 +1,23 @@
|
|||
module Jasmine
|
||||
class Config
|
||||
|
||||
# Add your overrides or custom config code here
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Note - this is necessary for rspec2, which has removed the backtrace
|
||||
module Jasmine
|
||||
class SpecBuilder
|
||||
def declare_spec(parent, spec)
|
||||
me = self
|
||||
example_name = spec["name"]
|
||||
@spec_ids << spec["id"]
|
||||
backtrace = @example_locations[parent.description + " " + example_name]
|
||||
parent.it example_name, {} do
|
||||
me.report_spec(spec["id"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
$:.unshift(ENV['JASMINE_GEM_PATH']) if ENV['JASMINE_GEM_PATH'] # for gem testing purposes
|
||||
|
||||
require 'rubygems'
|
||||
require 'jasmine'
|
||||
jasmine_config_overrides = File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config.rb'))
|
||||
require jasmine_config_overrides if File.exist?(jasmine_config_overrides)
|
||||
if Jasmine::rspec2?
|
||||
require 'rspec'
|
||||
else
|
||||
require 'spec'
|
||||
end
|
||||
|
||||
jasmine_config = Jasmine::Config.new
|
||||
spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
|
||||
|
||||
should_stop = false
|
||||
|
||||
if Jasmine::rspec2?
|
||||
RSpec.configuration.after(:suite) do
|
||||
spec_builder.stop if should_stop
|
||||
end
|
||||
else
|
||||
Spec::Runner.configure do |config|
|
||||
config.after(:suite) do
|
||||
spec_builder.stop if should_stop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
spec_builder.start
|
||||
should_stop = true
|
||||
spec_builder.declare_suites
|
Loading…
Reference in New Issue