From 8c4aac7f946b1a392238e098a8022a01ef861846 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 19 Jun 2013 15:06:23 -0400 Subject: [PATCH] Migrate all jasmine specs to Qunit. Removed Jasmine. --- Gemfile | 2 - Gemfile.lock | 19 -- Guardfile | 17 -- config/environments/test.rb | 3 - config/initializers/06-mini_profiler.rb | 4 +- docs/SOFTWARE.md | 1 - .../components/click_track_spec.js | 221 ------------------ spec/javascripts/components/utilities_spec.js | 63 ----- spec/javascripts/hacks.js | 21 -- spec/javascripts/models/report_spec.js | 98 -------- spec/javascripts/models/user_action_spec.js | 36 --- spec/javascripts/preload_store_spec.js | 106 --------- spec/javascripts/sanitize_spec.js | 15 -- spec/javascripts/spec.css | 3 - spec/javascripts/spec.js | 51 ---- .../components/click_track_test.js | 173 ++++++++++++++ test/javascripts/components/markdown_test.js | 8 +- .../components/preload_store_test.js | 73 ++++++ test/javascripts/components/utilities_test.js | 52 +++++ test/javascripts/integration/header_test.js | 2 + .../integration/list_topics_test.js | 2 + test/javascripts/models/report_test.js | 60 +++++ test/javascripts/models/user_action_test.js | 29 +++ test/javascripts/test_helper.js | 1 + 24 files changed, 401 insertions(+), 659 deletions(-) delete mode 100644 spec/javascripts/components/click_track_spec.js delete mode 100644 spec/javascripts/components/utilities_spec.js delete mode 100644 spec/javascripts/hacks.js delete mode 100644 spec/javascripts/models/report_spec.js delete mode 100644 spec/javascripts/models/user_action_spec.js delete mode 100644 spec/javascripts/preload_store_spec.js delete mode 100644 spec/javascripts/sanitize_spec.js delete mode 100644 spec/javascripts/spec.css delete mode 100644 spec/javascripts/spec.js create mode 100644 test/javascripts/components/click_track_test.js create mode 100644 test/javascripts/components/preload_store_test.js create mode 100644 test/javascripts/components/utilities_test.js create mode 100644 test/javascripts/models/report_test.js create mode 100644 test/javascripts/models/user_action_test.js diff --git a/Gemfile b/Gemfile index 5d32bfd68fd..7786a630a78 100644 --- a/Gemfile +++ b/Gemfile @@ -103,10 +103,8 @@ group :test, :development do gem 'certified', require: false gem 'fabrication', require: false gem 'qunit-rails' - gem 'guard-jasmine', require: false gem 'guard-rspec', require: false gem 'guard-spork', require: false - gem 'jasminerice' gem 'mocha', require: 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 diff --git a/Gemfile.lock b/Gemfile.lock index eae4c8a2729..455b1746446 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -151,13 +151,6 @@ GEM clockwork (0.5.0) tzinfo (~> 0.3.35) 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) daemons (1.1.9) debug_inspector (0.0.2) @@ -206,11 +199,6 @@ GEM lumberjack (>= 1.0.2) pry (>= 0.9.10) 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 (>= 1.0.0) jshint_on_rails (>= 1.0.2) @@ -221,8 +209,6 @@ GEM childprocess (>= 0.2.3) guard (>= 1.1) spork (>= 0.8.4) - haml (4.0.2) - tilt handlebars-source (1.0.0.rc4) has_ip_address (0.0.1) hashie (2.0.4) @@ -239,9 +225,6 @@ GEM image_size (1.1.2) image_sorcery (1.1.0) in_threads (1.1.1) - jasminerice (0.0.10) - coffee-rails - haml journey (1.0.4) jshint_on_rails (1.0.2) json (1.7.7) @@ -502,7 +485,6 @@ DEPENDENCIES fast_xs fastimage fog - guard-jasmine guard-jshint-on-rails guard-rspec guard-spork @@ -512,7 +494,6 @@ DEPENDENCIES hiredis image_optim image_sorcery - jasminerice jshint_on_rails librarian (>= 0.0.25) listen diff --git a/Guardfile b/Guardfile index a3e3ef8b9c2..beef7abeff1 100644 --- a/Guardfile +++ b/Guardfile @@ -3,23 +3,6 @@ require 'terminal-notifier-guard' if RUBY_PLATFORM.include?('darwin') phantom_path = File.expand_path('~/phantomjs/bin/phantomjs') 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 # see https://github.com/MrOrz/guard-jshint-on-rails guard 'jshint-on-rails', config_path: 'config/jshint.yml' do diff --git a/config/environments/test.rb b/config/environments/test.rb index 81f1e7f4b76..26dd7fc2c08 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -10,9 +10,6 @@ Discourse::Application.configure do # Configure static asset server for tests with Cache-Control for performance 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 config.whiny_nils = true diff --git a/config/initializers/06-mini_profiler.rb b/config/initializers/06-mini_profiler.rb index 44752832d85..3858000afb0 100644 --- a/config/initializers/06-mini_profiler.rb +++ b/config/initializers/06-mini_profiler.rb @@ -19,7 +19,7 @@ if defined?(Rack::MiniProfiler) (env['PATH_INFO'] !~ /^\/message-bus/) && (env['PATH_INFO'] !~ /topics\/timings/) && (env['PATH_INFO'] !~ /assets/) && - (env['PATH_INFO'] !~ /jasmine/) && + (env['PATH_INFO'] !~ /qunit/) && (env['PATH_INFO'] !~ /users\/.*\/avatar/) && (env['PATH_INFO'] !~ /srv\/status/) && (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 # 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| request = Rack::Request.new(env) id = request.cookies["_t"] || request.ip || "unknown" diff --git a/docs/SOFTWARE.md b/docs/SOFTWARE.md index bfe79c6f584..ba1a139ac14 100644 --- a/docs/SOFTWARE.md +++ b/docs/SOFTWARE.md @@ -37,7 +37,6 @@ The following Ruby Gems are used in Discourse: * [rspec](https://rubygems.org/gems/rspec) * [shoulda](https://rubygems.org/gems/shoulda) * [turn](https://rubygems.org/gems/turn) -* [jasminerice](https://rubygems.org/gems/jasminerice) * [fabrication](https://rubygems.org/gems/fabrication) * [mocha](https://rubygems.org/gems/mocha) * [simplecov](https://rubygems.org/gems/simplecov) diff --git a/spec/javascripts/components/click_track_spec.js b/spec/javascripts/components/click_track_spec.js deleted file mode 100644 index 359ef4b8993..00000000000 --- a/spec/javascripts/components/click_track_spec.js +++ /dev/null @@ -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 = [ - '
', - ' ', - '
'].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'); - }); - - }); - -}); diff --git a/spec/javascripts/components/utilities_spec.js b/spec/javascripts/components/utilities_spec.js deleted file mode 100644 index a9b10dfaff0..00000000000 --- a/spec/javascripts/components/utilities_spec.js +++ /dev/null @@ -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); - }); - - }); - -}); diff --git a/spec/javascripts/hacks.js b/spec/javascripts/hacks.js deleted file mode 100644 index 134a3647282..00000000000 --- a/spec/javascripts/hacks.js +++ /dev/null @@ -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(); - } - - $('
').appendTo($('body')).hide(); - - Discourse.SiteSettings = {} - - Discourse.Router.map(function() { - this.route("jasmine",{path: "/jasmine"}); - Discourse.routeBuilder.apply(this) - }); - } - -})() diff --git a/spec/javascripts/models/report_spec.js b/spec/javascripts/models/report_spec.js deleted file mode 100644 index 90b89714b8c..00000000000 --- a/spec/javascripts/models/report_spec.js +++ /dev/null @@ -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"); - }); - }); -}); diff --git a/spec/javascripts/models/user_action_spec.js b/spec/javascripts/models/user_action_spec.js deleted file mode 100644 index fb6e3c63005..00000000000 --- a/spec/javascripts/models/user_action_spec.js +++ /dev/null @@ -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); - }); - - }); - -}); diff --git a/spec/javascripts/preload_store_spec.js b/spec/javascripts/preload_store_spec.js deleted file mode 100644 index 02c87edc362..00000000000 --- a/spec/javascripts/preload_store_spec.js +++ /dev/null @@ -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'); - }); - }); - - }); - -}); \ No newline at end of file diff --git a/spec/javascripts/sanitize_spec.js b/spec/javascripts/sanitize_spec.js deleted file mode 100644 index b5126ec5b30..00000000000 --- a/spec/javascripts/sanitize_spec.js +++ /dev/null @@ -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("
"); - expect(sanitized).toBe("
"); - }); - - it("strips disallowed attributes", function(){ - var sanitized = sanitizeHtml("

hello

"); - expect(sanitized).toBe("

hello

"); - }); - -}); diff --git a/spec/javascripts/spec.css b/spec/javascripts/spec.css deleted file mode 100644 index 2358dbf4242..00000000000 --- a/spec/javascripts/spec.css +++ /dev/null @@ -1,3 +0,0 @@ -/* - - */ \ No newline at end of file diff --git a/spec/javascripts/spec.js b/spec/javascripts/spec.js deleted file mode 100644 index e353b71a8ba..00000000000 --- a/spec/javascripts/spec.js +++ /dev/null @@ -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 diff --git a/test/javascripts/components/click_track_test.js b/test/javascripts/components/click_track_test.js new file mode 100644 index 00000000000..e5af1a84c74 --- /dev/null +++ b/test/javascripts/components/click_track_test.js @@ -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([ + '
', + ' ', + '
'].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')); +}); diff --git a/test/javascripts/components/markdown_test.js b/test/javascripts/components/markdown_test.js index ed398eb7603..19599f6bde2 100644 --- a/test/javascripts/components/markdown_test.js +++ b/test/javascripts/components/markdown_test.js @@ -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"); @@ -93,3 +93,9 @@ test("Oneboxing", function() { }); +test("SanitizeHTML", function() { + + equal(sanitizeHtml("
"), "
"); + equal(sanitizeHtml("

hello

"), "

hello

"); + +}); \ No newline at end of file diff --git a/test/javascripts/components/preload_store_test.js b/test/javascripts/components/preload_store_test.js new file mode 100644 index 00000000000..36c056a7f7c --- /dev/null +++ b/test/javascripts/components/preload_store_test.js @@ -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(); + }); +}); diff --git a/test/javascripts/components/utilities_test.js b/test/javascripts/components/utilities_test.js new file mode 100644 index 00000000000..227af03096f --- /dev/null +++ b/test/javascripts/components/utilities_test.js @@ -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); +}); diff --git a/test/javascripts/integration/header_test.js b/test/javascripts/integration/header_test.js index 5ff8723868f..23a95a59024 100644 --- a/test/javascripts/integration/header_test.js +++ b/test/javascripts/integration/header_test.js @@ -13,6 +13,8 @@ module("Header", { test("/", function() { visit("/").then(function() { + expect(2); + ok(exists("header"), "The header was rendered"); ok(exists("#site-logo"), "The logo was shown"); }); diff --git a/test/javascripts/integration/list_topics_test.js b/test/javascripts/integration/list_topics_test.js index 440896618e9..5b94fdad4e8 100644 --- a/test/javascripts/integration/list_topics_test.js +++ b/test/javascripts/integration/list_topics_test.js @@ -13,6 +13,8 @@ module("List Topics", { test("/", function() { visit("/").then(function() { + expect(2); + ok(exists("#topic-list"), "The list of topics was rendered"); ok(count('#topic-list .topic-list-item') > 0, "has topics"); }); diff --git a/test/javascripts/models/report_test.js b/test/javascripts/models/report_test.js new file mode 100644 index 00000000000..2544d2e1c1c --- /dev/null +++ b/test/javascripts/models/report_test.js @@ -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/)); +}); diff --git a/test/javascripts/models/user_action_test.js b/test/javascripts/models/user_action_test.js new file mode 100644 index 00000000000..567c0271620 --- /dev/null +++ b/test/javascripts/models/user_action_test.js @@ -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); +}); diff --git a/test/javascripts/test_helper.js b/test/javascripts/test_helper.js index 41d3e5e41b9..7689a0ad317 100644 --- a/test/javascripts/test_helper.js +++ b/test/javascripts/test_helper.js @@ -52,6 +52,7 @@ sinon.config = { // Trick JSHint into allow document.write var d = document; +d.write(''); d.write('
'); d.write('');