Migrate all jasmine specs to Qunit. Removed Jasmine.

This commit is contained in:
Robin Ward 2013-06-19 15:06:23 -04:00
parent e263bb3c0a
commit 8c4aac7f94
24 changed files with 401 additions and 659 deletions

View file

@ -103,10 +103,8 @@ group :test, :development do
gem 'certified', require: false gem 'certified', require: false
gem 'fabrication', require: false gem 'fabrication', require: false
gem 'qunit-rails' gem 'qunit-rails'
gem 'guard-jasmine', require: false
gem 'guard-rspec', require: false gem 'guard-rspec', require: false
gem 'guard-spork', require: false gem 'guard-spork', require: false
gem 'jasminerice'
gem 'mocha', require: false gem 'mocha', require: false
gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false
gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false gem 'rb-inotify', '~> 0.9', require: RUBY_PLATFORM =~ /linux/i ? 'rb-inotify' : false

View file

@ -151,13 +151,6 @@ GEM
clockwork (0.5.0) clockwork (0.5.0)
tzinfo (~> 0.3.35) tzinfo (~> 0.3.35)
coderay (1.0.9) coderay (1.0.9)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.6.2)
connection_pool (1.0.0) connection_pool (1.0.0)
daemons (1.1.9) daemons (1.1.9)
debug_inspector (0.0.2) debug_inspector (0.0.2)
@ -206,11 +199,6 @@ GEM
lumberjack (>= 1.0.2) lumberjack (>= 1.0.2)
pry (>= 0.9.10) pry (>= 0.9.10)
thor (>= 0.14.6) thor (>= 0.14.6)
guard-jasmine (1.15.1)
childprocess
guard (>= 1.1.0)
multi_json
thor
guard-jshint-on-rails (0.0.2) guard-jshint-on-rails (0.0.2)
guard (>= 1.0.0) guard (>= 1.0.0)
jshint_on_rails (>= 1.0.2) jshint_on_rails (>= 1.0.2)
@ -221,8 +209,6 @@ GEM
childprocess (>= 0.2.3) childprocess (>= 0.2.3)
guard (>= 1.1) guard (>= 1.1)
spork (>= 0.8.4) spork (>= 0.8.4)
haml (4.0.2)
tilt
handlebars-source (1.0.0.rc4) handlebars-source (1.0.0.rc4)
has_ip_address (0.0.1) has_ip_address (0.0.1)
hashie (2.0.4) hashie (2.0.4)
@ -239,9 +225,6 @@ GEM
image_size (1.1.2) image_size (1.1.2)
image_sorcery (1.1.0) image_sorcery (1.1.0)
in_threads (1.1.1) in_threads (1.1.1)
jasminerice (0.0.10)
coffee-rails
haml
journey (1.0.4) journey (1.0.4)
jshint_on_rails (1.0.2) jshint_on_rails (1.0.2)
json (1.7.7) json (1.7.7)
@ -502,7 +485,6 @@ DEPENDENCIES
fast_xs fast_xs
fastimage fastimage
fog fog
guard-jasmine
guard-jshint-on-rails guard-jshint-on-rails
guard-rspec guard-rspec
guard-spork guard-spork
@ -512,7 +494,6 @@ DEPENDENCIES
hiredis hiredis
image_optim image_optim
image_sorcery image_sorcery
jasminerice
jshint_on_rails jshint_on_rails
librarian (>= 0.0.25) librarian (>= 0.0.25)
listen listen

View file

@ -3,23 +3,6 @@ require 'terminal-notifier-guard' if RUBY_PLATFORM.include?('darwin')
phantom_path = File.expand_path('~/phantomjs/bin/phantomjs') phantom_path = File.expand_path('~/phantomjs/bin/phantomjs')
phantom_path = nil unless File.exists?(phantom_path) phantom_path = nil unless File.exists?(phantom_path)
jasmine_options = {:phantomjs_bin => phantom_path, :server_env => :test}
if ENV['JASMINE_URL']
jasmine_options[:jasmine_url] = ENV['JASMINE_URL']
jasmine_options[:server] = :none
else
jasmine_options[:server] = :thin
jasmine_options[:port] = 8888
jasmine_options[:server_timeout] = 300
end
guard 'jasmine', jasmine_options do
watch(%r{spec/javascripts/spec\.js$}) { "spec/javascripts" }
watch(%r{spec/javascripts/.+_spec\.js$})
watch(%r{app/assets/javascripts/(.+?)\.js$}) { "spec/javascripts" }
end
# verify that we pass jshint # verify that we pass jshint
# see https://github.com/MrOrz/guard-jshint-on-rails # see https://github.com/MrOrz/guard-jshint-on-rails
guard 'jshint-on-rails', config_path: 'config/jshint.yml' do guard 'jshint-on-rails', config_path: 'config/jshint.yml' do

View file

@ -10,9 +10,6 @@ Discourse::Application.configure do
# Configure static asset server for tests with Cache-Control for performance # Configure static asset server for tests with Cache-Control for performance
config.serve_static_assets = true config.serve_static_assets = true
# Needed for jasmine specs to work
config.assets.debug = true
# Log error messages when you accidentally call methods on nil # Log error messages when you accidentally call methods on nil
config.whiny_nils = true config.whiny_nils = true

View file

@ -19,7 +19,7 @@ if defined?(Rack::MiniProfiler)
(env['PATH_INFO'] !~ /^\/message-bus/) && (env['PATH_INFO'] !~ /^\/message-bus/) &&
(env['PATH_INFO'] !~ /topics\/timings/) && (env['PATH_INFO'] !~ /topics\/timings/) &&
(env['PATH_INFO'] !~ /assets/) && (env['PATH_INFO'] !~ /assets/) &&
(env['PATH_INFO'] !~ /jasmine/) && (env['PATH_INFO'] !~ /qunit/) &&
(env['PATH_INFO'] !~ /users\/.*\/avatar/) && (env['PATH_INFO'] !~ /users\/.*\/avatar/) &&
(env['PATH_INFO'] !~ /srv\/status/) && (env['PATH_INFO'] !~ /srv\/status/) &&
(env['PATH_INFO'] !~ /commits-widget/) (env['PATH_INFO'] !~ /commits-widget/)
@ -27,7 +27,7 @@ if defined?(Rack::MiniProfiler)
# without a user provider our results will use the ip address for namespacing # without a user provider our results will use the ip address for namespacing
# with a load balancer in front this becomes really bad as some results can # with a load balancer in front this becomes really bad as some results can
# be stored associated with ip1 as the user and retrieved using ip2 causing 404s # be stored associated with ip1 as the user and retrieved using ip2 causing 404s
Rack::MiniProfiler.config.user_provider = lambda do |env| Rack::MiniProfiler.config.user_provider = lambda do |env|
request = Rack::Request.new(env) request = Rack::Request.new(env)
id = request.cookies["_t"] || request.ip || "unknown" id = request.cookies["_t"] || request.ip || "unknown"

View file

@ -37,7 +37,6 @@ The following Ruby Gems are used in Discourse:
* [rspec](https://rubygems.org/gems/rspec) * [rspec](https://rubygems.org/gems/rspec)
* [shoulda](https://rubygems.org/gems/shoulda) * [shoulda](https://rubygems.org/gems/shoulda)
* [turn](https://rubygems.org/gems/turn) * [turn](https://rubygems.org/gems/turn)
* [jasminerice](https://rubygems.org/gems/jasminerice)
* [fabrication](https://rubygems.org/gems/fabrication) * [fabrication](https://rubygems.org/gems/fabrication)
* [mocha](https://rubygems.org/gems/mocha) * [mocha](https://rubygems.org/gems/mocha)
* [simplecov](https://rubygems.org/gems/simplecov) * [simplecov](https://rubygems.org/gems/simplecov)

View file

@ -1,221 +0,0 @@
/*global expect:true describe:true it:true beforeEach:true afterEach:true spyOn:true */
describe("Discourse.ClickTrack", function() {
var track = Discourse.ClickTrack.trackClick,
clickEvent,
html = [
'<div id="topic" id="1337">',
' <article data-post-id="42" data-user-id="3141">',
' <a href="http://www.google.com">google.com</a>',
' <a class="lightbox back quote-other-topic" href="http://www.google.com">google.com</a>',
' <a id="with-badge" data-user-id="314" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <a id="with-badge-but-not-mine" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <div class="onebox-result">',
' <a id="inside-onebox" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <a id="inside-onebox-forced" class="track-link" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' </div>',
' <a id="same-site" href="http://discuss.domain.com">forum</a>',
' </article>',
'</div>'].join("\n");
var generateClickEventOn = function(selector) {
return $.Event("click", { currentTarget: $(selector)[0] });
}
beforeEach(function() {
$('body').html(html);
});
afterEach(function() {
$('#topic').remove();
});
describe("lightboxes", function() {
beforeEach(function() {
clickEvent = generateClickEventOn('.lightbox');
});
it("does not track clicks on lightboxes", function() {
expect(track(clickEvent)).toBe(true);
});
it("does not call preventDefault", function() {
spyOn(clickEvent, "preventDefault");
track(clickEvent);
expect(clickEvent.preventDefault).not.toHaveBeenCalled();
});
});
it("calls preventDefault", function() {
clickEvent = generateClickEventOn('a');
spyOn(clickEvent, "preventDefault");
track(clickEvent);
expect(clickEvent.preventDefault).toHaveBeenCalled();
});
it("does not track clicks on back buttons", function() {
clickEvent = generateClickEventOn('.back');
expect(track(clickEvent)).toBe(true);
});
it("does not track clicks on quote buttons", function() {
clickEvent = generateClickEventOn('.quote-other-topic');
expect(track(clickEvent)).toBe(true);
});
it("removes the href and put it as a data attribute", function() {
clickEvent = generateClickEventOn('a');
track(clickEvent);
var $link = $('a').first();
expect($link.hasClass('no-href')).toBe(true);
expect($link.data('href')).toEqual("http://www.google.com");
expect($link.attr('href')).toBeUndefined();
expect($link.data('auto-route')).toBe(true);
});
describe("badges", function() {
it("does not update badge clicks on my own link", function() {
spyOn(Discourse.User, 'current').andReturn(314);
spyOn(Discourse, "get").andReturn(314);
track(generateClickEventOn('#with-badge'));
var $badge = $('span.badge', $('#with-badge').first());
expect(parseInt($badge.html(), 10)).toEqual(1);
});
it("does not update badge clicks on links in my own post", function() {
spyOn(Discourse.User, 'current').andReturn(3141);
track(generateClickEventOn('#with-badge-but-not-mine'));
var $badge = $('span.badge', $('#with-badge-but-not-mine').first());
expect(parseInt($badge.html(), 10)).toEqual(1);
});
describe("oneboxes", function() {
it("does not update badge clicks in oneboxes", function() {
track(generateClickEventOn('#inside-onebox'));
var $badge = $('span.badge', $('#inside-onebox').first());
expect(parseInt($badge.html(), 10)).toEqual(1);
});
it("updates badge clicks in oneboxes when forced", function() {
track(generateClickEventOn('#inside-onebox-forced'));
var $badge = $('span.badge', $('#inside-onebox-forced').first());
expect(parseInt($badge.html(), 10)).toEqual(2);
});
});
it("updates badge clicks", function() {
track(generateClickEventOn('#with-badge'));
var $badge = $('span.badge', $('#with-badge').first());
expect(parseInt($badge.html(), 10)).toEqual(2);
});
});
describe("right click", function() {
beforeEach(function(){
clickEvent = generateClickEventOn('a');
clickEvent.which = 3;
});
it("detects right clicks", function() {
expect(track(clickEvent)).toBe(true);
});
it("changes the href", function() {
track(clickEvent);
var $link = $('a').first();
expect($link.attr('href')).toEqual("http://www.google.com");
});
it("tracks external right clicks", function() {
Discourse.SiteSettings.track_external_right_clicks = true;
track(clickEvent);
var $link = $('a').first();
expect($link.attr('href')).toEqual("/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42");
// reset
Discourse.SiteSettings.track_external_right_clicks = false;
});
});
describe("new tab", function() {
beforeEach(function(){
clickEvent = generateClickEventOn('a');
spyOn(Discourse, 'ajax');
spyOn(window, 'open');
});
var expectItOpensInANewTab = function() {
expect(track(clickEvent)).toBe(false);
expect(Discourse.ajax).toHaveBeenCalled();
expect(window.open).toHaveBeenCalledWith('http://www.google.com', '_blank');
};
it("opens in a new tab when pressing shift", function() {
clickEvent.shiftKey = true;
expectItOpensInANewTab();
});
it("opens in a new tab when pressing meta", function() {
clickEvent.metaKey = true;
expectItOpensInANewTab();
});
it("opens in a new tab when pressing ctrl", function() {
clickEvent.ctrlKey = true;
expectItOpensInANewTab();
});
it("opens in a new tab when middle clicking", function() {
clickEvent.which = 2;
expectItOpensInANewTab();
});
});
it("tracks via AJAX if we're on the same site", function() {
// setup
clickEvent = generateClickEventOn('#same-site');
spyOn(Discourse, 'ajax');
spyOn(Discourse.URL, 'routeTo');
spyOn(Discourse.URL, 'origin').andReturn('http://discuss.domain.com');
// test
expect(track(clickEvent)).toBe(false);
expect(Discourse.ajax).toHaveBeenCalled();
expect(Discourse.URL.routeTo).toHaveBeenCalledWith('http://discuss.domain.com');
});
describe("tracks via custom URL", function() {
beforeEach(function() {
clickEvent = generateClickEventOn('a');
});
it("in another window", function() {
// spies
spyOn(Discourse.User, 'current').andReturn(true);
spyOn(window, 'open').andCallFake(function() { return { focus: function() {} } });
spyOn(window, 'focus');
// test
expect(track(clickEvent)).toBe(false);
expect(window.open).toHaveBeenCalledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42', '_blank');
});
it("in the same window", function() {
spyOn(Discourse.URL, 'redirectTo');
expect(track(clickEvent)).toBe(false);
expect(Discourse.URL.redirectTo).toHaveBeenCalledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42');
});
});
});

View file

@ -1,63 +0,0 @@
/*global waitsFor:true expect:true describe:true beforeEach:true it:true spyOn:true */
describe("Discourse.Utilities", function() {
describe("emailValid", function() {
it("allows upper case in first part of emails", function() {
expect(Discourse.Utilities.emailValid('Bob@example.com')).toBe(true);
});
it("allows upper case in domain of emails", function() {
expect(Discourse.Utilities.emailValid('bob@EXAMPLE.com')).toBe(true);
});
});
describe("validateFilesForUpload", function() {
it("returns false when file is undefined", function() {
expect(Discourse.Utilities.validateFilesForUpload(null)).toBe(false);
expect(Discourse.Utilities.validateFilesForUpload(undefined)).toBe(false);
});
it("returns false when file there is no file", function() {
expect(Discourse.Utilities.validateFilesForUpload([])).toBe(false);
});
it("supports only one file", function() {
spyOn(bootbox, 'alert');
spyOn(Em.String, 'i18n');
expect(Discourse.Utilities.validateFilesForUpload([1, 2])).toBe(false);
expect(bootbox.alert).toHaveBeenCalled();
expect(Em.String.i18n).toHaveBeenCalledWith('post.errors.upload_too_many_images');
});
it("supports only an image", function() {
var html = { type: "text/html" };
spyOn(bootbox, 'alert');
spyOn(Em.String, 'i18n');
expect(Discourse.Utilities.validateFilesForUpload([html])).toBe(false);
expect(bootbox.alert).toHaveBeenCalled();
expect(Em.String.i18n).toHaveBeenCalledWith('post.errors.only_images_are_supported');
});
it("prevents the upload of a too large image", function() {
var image = { type: "image/png", size: 10 * 1024 };
Discourse.SiteSettings.max_upload_size_kb = 5;
spyOn(bootbox, 'alert');
spyOn(Em.String, 'i18n');
expect(Discourse.Utilities.validateFilesForUpload([image])).toBe(false);
expect(bootbox.alert).toHaveBeenCalled();
expect(Em.String.i18n).toHaveBeenCalledWith('post.errors.upload_too_large', { max_size_kb: 5 });
});
it("works", function() {
var image = { type: "image/png", size: 10 * 1024 };
Discourse.SiteSettings.max_upload_size_kb = 15;
expect(Discourse.Utilities.validateFilesForUpload([image])).toBe(true);
});
});
});

View file

@ -1,21 +0,0 @@
// hacks for ember, this sets up our app for testing
(function(){
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
$('<div id="main"><div class="rootElement"></div></div>').appendTo($('body')).hide();
Discourse.SiteSettings = {}
Discourse.Router.map(function() {
this.route("jasmine",{path: "/jasmine"});
Discourse.routeBuilder.apply(this)
});
}
})()

View file

@ -1,98 +0,0 @@
/*global waitsFor:true expect:true describe:true beforeEach:true it:true */
describe("Discourse.Report", function() {
function dateString(days) {
return moment().subtract("days", days).format('YYYY-MM-DD');
}
function reportWithData(data) {
var arr = [];
_.each(data,function(val,index) {
arr.push({x: dateString(index), y: val});
});
return Discourse.Report.create({ type: 'topics', data: arr });
}
describe("todayCount", function() {
it("returns the correct value", function() {
expect( reportWithData([5,4,3,2,1]).get('todayCount') ).toBe(5);
});
});
describe("yesterdayCount", function() {
it("returns the correct value", function() {
expect( reportWithData([5,4,3,2,1]).get('yesterdayCount') ).toBe(4);
});
});
describe("sumDays", function() {
it("adds the values for the given range of days, inclusive", function() {
expect( reportWithData([1,2,3,5,8,13]).sumDays(2,4) ).toBe(16);
});
});
describe("lastSevenDaysCount", function() {
it("returns the correct value", function() {
expect( reportWithData([100,9,8,7,6,5,4,3,200,300,400]).get('lastSevenDaysCount') ).toBe(42);
});
});
describe("percentChangeString", function() {
it("returns correct value when value increased", function() {
expect( reportWithData([]).percentChangeString(8,5) ).toBe("+60%");
});
it("returns correct value when value decreased", function() {
expect( reportWithData([]).percentChangeString(2,8) ).toBe("-75%");
});
it("returns 0 when value is unchanged", function() {
expect( reportWithData([]).percentChangeString(8,8) ).toBe("0%");
});
it("returns Infinity when previous value was 0", function() {
expect( reportWithData([]).percentChangeString(8,0) ).toBe(null);
});
it("returns -100 when yesterday's value was 0", function() {
expect( reportWithData([]).percentChangeString(0,8) ).toBe('-100%');
});
it("returns NaN when both yesterday and the previous day were both 0", function() {
expect( reportWithData([]).percentChangeString(0,0) ).toBe(null);
});
});
describe("yesterdayCountTitle", function() {
it("displays percent change and previous value", function(){
var title = reportWithData([6,8,5,2,1]).get('yesterdayCountTitle')
expect( title.indexOf('+60%') ).not.toBe(-1);
expect( title ).toMatch("Was 5");
});
it("handles when two days ago was 0", function() {
var title = reportWithData([6,8,0,2,1]).get('yesterdayCountTitle')
expect( title ).toMatch("Was 0");
expect( title ).not.toMatch("%");
});
});
describe("sevenDayCountTitle", function() {
it("displays percent change and previous value", function(){
var title = reportWithData([100,1,1,1,1,1,1,1,2,2,2,2,2,2,2,100,100]).get('sevenDayCountTitle');
expect( title ).toMatch("-50%");
expect( title ).toMatch("Was 14");
});
});
describe("thirtyDayCountTitle", function() {
it("displays percent change and previous value", function(){
var report = reportWithData([5,5,5,5]);
report.set('prev30Days', 10);
var title = report.get('thirtyDayCountTitle');
expect( title.indexOf('+50%') ).not.toBe(-1);
expect( title ).toMatch("Was 10");
});
});
});

View file

@ -1,36 +0,0 @@
/*global waitsFor:true expect:true describe:true beforeEach:true it:true */
describe("Discourse.UserAction", function() {
describe("collapseStream", function() {
it("collapses all likes", function() {
var actions = [
Discourse.UserAction.create({
action_type: Discourse.UserAction.LIKE,
topic_id: 1,
user_id: 1,
post_number: 1
}), Discourse.UserAction.create({
action_type: Discourse.UserAction.EDIT,
topic_id: 2,
user_id: 1,
post_number: 1
}), Discourse.UserAction.create({
action_type: Discourse.UserAction.LIKE,
topic_id: 1,
user_id: 2,
post_number: 1
})
];
actions = Discourse.UserAction.collapseStream(actions);
expect(actions.length).toBe(2);
expect(actions[0].get("children").length).toBe(1);
expect(actions[0].get("children")[0].items.length).toBe(2);
});
});
});

View file

@ -1,106 +0,0 @@
/*global waitsFor:true expect:true describe:true beforeEach:true it:true runs:true */
describe("PreloadStore", function() {
beforeEach(function() {
PreloadStore.store('bane', 'evil');
});
describe('get', function() {
it("returns undefined if the key doesn't exist", function() {
expect(PreloadStore.get('joker')).toBe(undefined);
});
it("returns the value if the key exists", function() {
expect(PreloadStore.get('bane')).toBe('evil');
});
});
describe('remove', function() {
it("removes the value if the key exists", function() {
PreloadStore.remove('bane');
expect(PreloadStore.get('bane')).toBe(undefined);
});
});
describe('getAndRemove', function() {
it("returns a promise that resolves to null", function() {
var done, storeResult;
done = storeResult = null;
PreloadStore.getAndRemove('joker').then(function(result) {
done = true;
storeResult = result;
});
waitsFor((function() { return done; }), "Promise never resolved", 1000);
runs(function() {
expect(storeResult).toBe(null);
});
});
it("returns a promise that resolves to the result of the finder", function() {
var done, finder, storeResult;
done = storeResult = null;
finder = function() { return 'evil'; };
PreloadStore.getAndRemove('joker', finder).then(function(result) {
done = true;
storeResult = result;
});
waitsFor((function() { return done; }), "Promise never resolved", 1000);
runs(function() {
expect(storeResult).toBe('evil');
});
});
it("returns a promise that resolves to the result of the finder's promise", function() {
var done, finder, storeResult;
done = storeResult = null;
finder = function() {
return Ember.Deferred.promise(function(promise) { promise.resolve('evil'); });
};
PreloadStore.getAndRemove('joker', finder).then(function(result) {
done = true;
storeResult = result;
});
waitsFor((function() { return done; }), "Promise never resolved", 1000);
runs(function() {
expect(storeResult).toBe('evil');
});
});
it("returns a promise that resolves to the result of the finder's rejected promise", function() {
var done, finder, storeResult;
done = storeResult = null;
finder = function() {
return Ember.Deferred.promise(function(promise) { promise.reject('evil'); });
};
PreloadStore.getAndRemove('joker', finder).then(null, function(rejectedResult) {
done = true;
storeResult = rejectedResult;
});
waitsFor((function() { return done; }), "Promise never rejected", 1000);
runs(function() {
expect(storeResult).toBe('evil');
});
});
it("returns a promise that resolves to 'evil'", function() {
var done, storeResult;
done = storeResult = null;
PreloadStore.getAndRemove('bane').then(function(result) {
done = true;
storeResult = result;
});
waitsFor((function() { return done; }), "Promise never resolved", 1000);
runs(function() {
expect(storeResult).toBe('evil');
});
});
});
});

View file

@ -1,15 +0,0 @@
/*global waitsFor:true expect:true describe:true beforeEach:true it:true sanitizeHtml:true */
describe("sanitize", function(){
it("strips all script tags", function(){
var sanitized = sanitizeHtml("<div><script>alert('hi');</script></div>");
expect(sanitized).toBe("<div></div>");
});
it("strips disallowed attributes", function(){
var sanitized = sanitizeHtml("<div><p class=\"funky\" wrong='1'>hello</p></div>");
expect(sanitized).toBe("<div><p class=\"funky\">hello</p></div>");
});
});

View file

@ -1,3 +0,0 @@
/*
*/

View file

@ -1,51 +0,0 @@
//= require env
//= require ../../app/assets/javascripts/preload_store.js
// probe framework first
//= require ../../app/assets/javascripts/discourse/components/probes.js
// Externals we need to load first
//= require ../../app/assets/javascripts/external/jquery-1.9.1.js
//= require ../../app/assets/javascripts/external/jquery.ui.widget.js
//= require ../../app/assets/javascripts/external/handlebars-1.0.rc.4.js
//= require ../../app/assets/javascripts/external_production/ember.js
//= require ../../app/assets/javascripts/external_production/group-helper.js
//= require ../../app/assets/javascripts/locales/i18n
//= require ../../app/assets/javascripts/locales/date_locales.js
//= require ../../app/assets/javascripts/discourse/helpers/i18n_helpers
//= require ../../app/assets/javascripts/locales/en
//
// Pagedown customizations
//= require ../../app/assets/javascripts/pagedown_custom.js
// The rest of the externals
//= require_tree ../../app/assets/javascripts/external
//= require ../../app/assets/javascripts/discourse
// Stuff we need to load first
//= require_tree ../../app/assets/javascripts/discourse/mixins
//= require ../../app/assets/javascripts/discourse/components/debounce
//= require ../../app/assets/javascripts/discourse/controllers/controller
//= require ../../app/assets/javascripts/discourse/views/modal/modal_body_view
//= require ../../app/assets/javascripts/discourse/models/model
//= require ../../app/assets/javascripts/discourse/routes/discourse_route
//= require_tree ../../app/assets/javascripts/discourse/controllers
//= require_tree ../../app/assets/javascripts/discourse/components
//= require_tree ../../app/assets/javascripts/discourse/models
//= require_tree ../../app/assets/javascripts/discourse/views
//= require_tree ../../app/assets/javascripts/discourse/helpers
//= require_tree ../../app/assets/javascripts/discourse/templates
//= require_tree ../../app/assets/javascripts/discourse/routes
//= require_tree ../../app/assets/javascripts/admin/models
//= require_tree ../../app/assets/javascripts/defer
//= require_tree .
//= require hacks

View file

@ -0,0 +1,173 @@
/*global module:true test:true ok:true visit:true equal:true exists:true count:true equal:true present:true sinon:true blank:true */
module("Discourse.ClickTrack", {
setup: function() {
// Prevent any of these tests from navigating away
this.win = {focus: function() { } };
this.redirectTo = sinon.stub(Discourse.URL, "redirectTo");
sinon.stub(Discourse, "ajax");
this.windowOpen = sinon.stub(window, "open").returns(this.win);
sinon.stub(this.win, "focus");
$('#qunit-scratch').html([
'<div id="topic" id="1337">',
' <article data-post-id="42" data-user-id="3141">',
' <a href="http://www.google.com">google.com</a>',
' <a class="lightbox back quote-other-topic" href="http://www.google.com">google.com</a>',
' <a id="with-badge" data-user-id="314" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <a id="with-badge-but-not-mine" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <div class="onebox-result">',
' <a id="inside-onebox" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' <a id="inside-onebox-forced" class="track-link" href="http://www.google.com">google.com<span class="badge">1</span></a>',
' </div>',
' <a id="same-site" href="http://discuss.domain.com">forum</a>',
' </article>',
'</div>'].join("\n"));
},
teardown: function() {
$('#topic').remove();
$('#qunit-scratch').html('');
Discourse.URL.redirectTo.restore();
Discourse.ajax.restore();
window.open.restore();
this.win.focus.restore();
}
});
var track = Discourse.ClickTrack.trackClick;
var generateClickEventOn = function(selector) {
return $.Event("click", { currentTarget: $(selector)[0] });
}
test("does not track clicks on lightboxes", function() {
var clickEvent = generateClickEventOn('.lightbox');
this.stub(clickEvent, "preventDefault");
ok(track(clickEvent));
ok(!clickEvent.preventDefault.calledOnce);
});
test("it calls preventDefault when clicking on an a", function() {
var clickEvent = generateClickEventOn('a');
this.stub(clickEvent, "preventDefault");
track(clickEvent);
ok(clickEvent.preventDefault.calledOnce);
ok(Discourse.URL.redirectTo.calledOnce);
});
test("does not track clicks on back buttons", function() {
ok(track(generateClickEventOn('.back')))
});
test("does not track clicks on quote buttons", function() {
ok(track(generateClickEventOn('.quote-other-topic')))
});
test("removes the href and put it as a data attribute", function() {
track(generateClickEventOn('a'));
var $link = $('a').first();
ok($link.hasClass('no-href'));
equal($link.data('href'), 'http://www.google.com');
blank($link.attr('href'));
ok($link.data('auto-route'));
ok(Discourse.URL.redirectTo.calledOnce);
});
var badgeClickCount = function(id, expected) {
track(generateClickEventOn('#' + id));
var $badge = $('span.badge', $('#' + id).first());
equal(parseInt($badge.html(), 10), expected);
};
test("does not update badge clicks on my own link", function() {
this.stub(Discourse.User, 'current').returns(314);
badgeClickCount('with-badge', 1);
});
test("does not update badge clicks in my own post", function() {
this.stub(Discourse.User, 'current').returns(3141);
badgeClickCount('with-badge-but-not-mine', 1);
});
test("updates badge counts correctly", function() {
badgeClickCount('inside-onebox', 1);
badgeClickCount('inside-onebox-forced', 2);
badgeClickCount('with-badge', 2);
});
var trackRightClick = function() {
var clickEvent = generateClickEventOn('a')
clickEvent.which = 3;
return track(clickEvent);
};
test("right clicks change the href", function() {
ok(trackRightClick());
equal($('a').first().prop('href'), "http://www.google.com/");
});
test("right clicks are tracked", function() {
Discourse.SiteSettings.track_external_right_clicks = true;
trackRightClick();
equal($('a').first().attr('href'), "/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42");
Discourse.SiteSettings.track_external_right_clicks = false;
});
var expectToOpenInANewTab = function(clickEvent) {
ok(!track(clickEvent));
ok(Discourse.ajax.calledOnce);
ok(window.open.calledOnce);
};
test("it opens in a new tab when pressing shift", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.shiftKey = true;
expectToOpenInANewTab(clickEvent);
});
test("it opens in a new tab when pressing meta", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.metaKey = true;
expectToOpenInANewTab(clickEvent);
});
test("it opens in a new tab when pressing meta", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.ctrlKey = true;
expectToOpenInANewTab(clickEvent);
});
test("it opens in a new tab when pressing meta", function() {
var clickEvent = generateClickEventOn('a');
clickEvent.which = 2;
expectToOpenInANewTab(clickEvent);
});
test("tracks via AJAX if we're on the same site", function() {
this.stub(Discourse.URL, "routeTo");
this.stub(Discourse.URL, "origin").returns("http://discuss.domain.com");
ok(!track(generateClickEventOn('#same-site')));
ok(Discourse.ajax.calledOnce);
ok(Discourse.URL.routeTo.calledOnce);
});
test("tracks custom urls when opening in another window", function() {
var clickEvent = generateClickEventOn('a');
this.stub(Discourse.User, "current").returns(true);
ok(!track(clickEvent));
ok(this.windowOpen.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42', '_blank'));
});
test("tracks custom urls when opening in another window", function() {
var clickEvent = generateClickEventOn('a');
ok(!track(clickEvent));
ok(this.redirectTo.calledWith('/clicks/track?url=http%3A%2F%2Fwww.google.com&post_id=42'));
});

View file

@ -1,4 +1,4 @@
/*global module:true test:true ok:true visit:true equal:true exists:true count:true equal:true present:true md5:true */ /*global module:true test:true ok:true visit:true equal:true exists:true count:true equal:true present:true md5:true sanitizeHtml:true */
module("Discourse.Markdown"); module("Discourse.Markdown");
@ -93,3 +93,9 @@ test("Oneboxing", function() {
}); });
test("SanitizeHTML", function() {
equal(sanitizeHtml("<div><script>alert('hi');</script></div>"), "<div></div>");
equal(sanitizeHtml("<div><p class=\"funky\" wrong='1'>hello</p></div>"), "<div><p class=\"funky\">hello</p></div>");
});

View file

@ -0,0 +1,73 @@
/*global module:true test:true ok:true visit:true expect:true exists:true equal:true count:true present:true asyncTest:true blank:true */
module("Discourse.PreloadStore", {
setup: function() {
PreloadStore.store('bane', 'evil');
}
});
test("get", function() {
blank(PreloadStore.get('joker'), "returns blank for a missing key");
equal(PreloadStore.get('bane'), 'evil', "returns the value for that key");
});
test("remove", function() {
PreloadStore.remove('bane');
blank(PreloadStore.get('bane'), "removes the value if the key exists");
});
asyncTest("getAndRemove returns a promise that resolves to null", function() {
expect(1);
PreloadStore.getAndRemove('joker').then(function(result) {
blank(result);
start();
});
});
asyncTest("getAndRemove returns a promise that resolves to the result of the finder", function() {
expect(1);
var finder = function() { return 'batdance'; };
PreloadStore.getAndRemove('joker', finder).then(function(result) {
equal(result, 'batdance');
start();
});
});
asyncTest("getAndRemove returns a promise that resolves to the result of the finder's promise", function() {
expect(1);
var finder = function() {
return Ember.Deferred.promise(function(promise) { promise.resolve('hahahah'); });
};
PreloadStore.getAndRemove('joker', finder).then(function(result) {
equal(result, 'hahahah');
start();
});
});
asyncTest("returns a promise that rejects with the result of the finder's rejected promise", function() {
expect(1);
var finder = function() {
return Ember.Deferred.promise(function(promise) { promise.reject('error'); });
};
PreloadStore.getAndRemove('joker', finder).then(null, function(result) {
equal(result, 'error');
start();
});
});
asyncTest("returns a promise that resolves to 'evil'", function() {
expect(1);
PreloadStore.getAndRemove('bane').then(function(result) {
equal(result, 'evil');
start();
});
});

View file

@ -0,0 +1,52 @@
/*global module:true test:true ok:true visit:true expect:true exists:true count:true equal:true */
module("Discourse.Utilities");
var utils = Discourse.Utilities;
test("emailValid", function() {
ok(utils.emailValid('Bob@example.com'), "allows upper case in the first part of emails");
ok(utils.emailValid('bob@EXAMPLE.com'), "allows upper case in the email domain");
});
var validUpload = utils.validateFilesForUpload;
test("validateFilesForUpload", function() {
ok(!validUpload(null), "no files are invalid");
ok(!validUpload(undefined), "undefined files are invalid");
ok(!validUpload([]), "empty array of files is invalid");
});
test("uploading one file", function() {
this.stub(bootbox, "alert");
ok(!validUpload([1, 2]));
ok(bootbox.alert.calledOnce);
});
test("ensures an image upload", function() {
var html = { type: "text/html" };
this.stub(bootbox, "alert");
ok(!validUpload([html]));
ok(bootbox.alert.calledOnce);
});
test("prevents files that are too big from being uploaded", function() {
var image = { type: "image/png", size: 10 * 1024 };
Discourse.SiteSettings.max_upload_size_kb = 5;
this.stub(bootbox, "alert");
ok(!validUpload([image]));
ok(bootbox.alert.calledOnce);
});
test("allows valid uploads to go through", function() {
var image = { type: "image/png", size: 10 * 1024 };
Discourse.SiteSettings.max_upload_size_kb = 15;
this.stub(bootbox, "alert");
ok(validUpload([image]));
ok(!bootbox.alert.calledOnce);
});

View file

@ -13,6 +13,8 @@ module("Header", {
test("/", function() { test("/", function() {
visit("/").then(function() { visit("/").then(function() {
expect(2);
ok(exists("header"), "The header was rendered"); ok(exists("header"), "The header was rendered");
ok(exists("#site-logo"), "The logo was shown"); ok(exists("#site-logo"), "The logo was shown");
}); });

View file

@ -13,6 +13,8 @@ module("List Topics", {
test("/", function() { test("/", function() {
visit("/").then(function() { visit("/").then(function() {
expect(2);
ok(exists("#topic-list"), "The list of topics was rendered"); ok(exists("#topic-list"), "The list of topics was rendered");
ok(count('#topic-list .topic-list-item') > 0, "has topics"); ok(count('#topic-list .topic-list-item') > 0, "has topics");
}); });

View file

@ -0,0 +1,60 @@
/*global module:true test:true ok:true visit:true expect:true exists:true count:true equal:true blank:true */
module("Discourse.Report");
function reportWithData(data) {
return Discourse.Report.create({
type: 'topics',
data: _.map(data, function(val, index) {
return {x: moment().subtract("days", index).format('YYYY-MM-DD'), y: val};
})
});
}
test("counts", function() {
var report = reportWithData([5, 4, 3, 2, 1, 100, 99, 98, 1000]);
equal(report.get('todayCount'), 5);
equal(report.get('yesterdayCount'), 4);
equal(report.sumDays(2, 4), 6, "adds the values for the given range of days, inclusive");
equal(report.get('lastSevenDaysCount'), 307, "sums 7 days excluding today");
});
test("percentChangeString", function() {
var report = reportWithData([]);
equal(report.percentChangeString(8, 5), "+60%", "value increased");
equal(report.percentChangeString(2, 8), "-75%", "value decreased");
equal(report.percentChangeString(8, 8), "0%", "value unchanged");
blank(report.percentChangeString(8, 0), "returns blank when previous value was 0");
equal(report.percentChangeString(0, 8), "-100%", "yesterday was 0");
blank(report.percentChangeString(0, 0), "returns blank when both were 0");
});
test("yesterdayCountTitle with valid values", function() {
var title = reportWithData([6,8,5,2,1]).get('yesterdayCountTitle');
ok(title.indexOf('+60%') !== -1);
ok(title.match(/Was 5/));
});
test("yesterdayCountTitle when two days ago was 0", function() {
var title = reportWithData([6,8,0,2,1]).get('yesterdayCountTitle');
equal(title.indexOf('%'), -1);
ok(title.match(/Was 0/));
});
test("sevenDayCountTitle", function() {
var title = reportWithData([100,1,1,1,1,1,1,1,2,2,2,2,2,2,2,100,100]).get('sevenDayCountTitle');
ok(title.match(/-50%/));
ok(title.match(/Was 14/));
});
test("thirtyDayCountTitle", function() {
var report = reportWithData([5,5,5,5]);
report.set('prev30Days', 10);
var title = report.get('thirtyDayCountTitle');
ok(title.indexOf('+50%') !== -1);
ok(title.match(/Was 10/));
});

View file

@ -0,0 +1,29 @@
/*global module:true test:true ok:true visit:true expect:true exists:true count:true present:true equal:true */
module("Discourse.UserAction");
test("collapsing likes", function () {
var actions = Discourse.UserAction.collapseStream([
Discourse.UserAction.create({
action_type: Discourse.UserAction.LIKE,
topic_id: 1,
user_id: 1,
post_number: 1
}), Discourse.UserAction.create({
action_type: Discourse.UserAction.EDIT,
topic_id: 2,
user_id: 1,
post_number: 1
}), Discourse.UserAction.create({
action_type: Discourse.UserAction.LIKE,
topic_id: 1,
user_id: 2,
post_number: 1
})
]);
equal(actions.length, 2);
equal(actions[0].get('children.length'), 1);
equal(actions[0].get('children')[0].items.length, 2);
});

View file

@ -52,6 +52,7 @@ sinon.config = {
// Trick JSHint into allow document.write // Trick JSHint into allow document.write
var d = document; var d = document;
d.write('<div id="qunit-scratch" style="display:none"></div>');
d.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>'); d.write('<div id="ember-testing-container"><div id="ember-testing"></div></div>');
d.write('<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>'); d.write('<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>');