From 2c37950472b4874ca13be47c4864276f3dd34ceb Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Fri, 27 Apr 2012 17:38:45 -0400 Subject: [PATCH 1/3] Add Jasmine to do a JavaScript unit test --- requirements.txt | 1 + settings.py | 4 +++ templates/coffee/README.md | 59 ++++++++++++++++++++++++++++++++++++++ urls.py | 4 +++ 4 files changed, 68 insertions(+) create mode 100644 templates/coffee/README.md diff --git a/requirements.txt b/requirements.txt index 6e13b88463..8e701ce335 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ path.py django_debug_toolbar django-masquerade pyfilesystem +django-jasmine diff --git a/settings.py b/settings.py index f20f51ffb8..0828f75595 100644 --- a/settings.py +++ b/settings.py @@ -147,6 +147,7 @@ INSTALLED_APPS = ( 'perfstats', 'util', 'masquerade', + 'django_jasmine', # Uncomment the next line to enable the admin: # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: @@ -347,6 +348,7 @@ PROJECT_ROOT = os.path.dirname(__file__) TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', + 'django.core.context_processors.static', 'askbot.context.application_settings', #'django.core.context_processors.i18n', 'askbot.user_messages.context_processors.user_messages',#must be before auth @@ -683,3 +685,5 @@ if MAKO_MODULE_DIR == None: djcelery.setup_loader() +# Jasmine Settings +JASMINE_TEST_DIRECTORY = PROJECT_DIR+'/templates/coffee' diff --git a/templates/coffee/README.md b/templates/coffee/README.md new file mode 100644 index 0000000000..6c26529a5b --- /dev/null +++ b/templates/coffee/README.md @@ -0,0 +1,59 @@ +CoffeeScript +============ + +This folder contains the CoffeeScript file that will be compiled to the static +directory. By default, we're compile and merge all the files ending `.coffee` +into `static/js/application.js`. + +Install the Compiler +-------------------- + +CoffeeScript compiler are written in JavaScript. You'll need to install Node and +npm (Node Package Manager) to be able to install the CoffeeScript compiler. + +### Mac OS X + +Install Node via Homebrew, then use npm: + + brew install node + curl http://npmjs.org/install.sh | sh + npm install -g git://github.com/jashkenas/coffee-script.git + +(Note that we're using the edge version of CoffeeScript for now, as there was +some issue with directory watching in 1.3.1.) + +Try to run `coffee` and make sure you get a coffee prompt. + +### Debian/Ubuntu + +Conveniently, you can install Node via `apt-get`, then use npm: + + sudo apt-get install nodejs npm && + sudo npm install -g git://github.com/jashkenas/coffee-script.git + +Compiling +--------- + +Run this command in the `mitx` directory to easily make the compiler watch for +changes in your file, and join the result into `application.js`: + + coffee -j static/js/application.js -cw templates/coffee/src + +Please note that the compiler will not be able to detect the file that get added +after you've ran the command, so you'll need to restart the compiler if there's +a new CoffeeScript file. + +Testing +======= + +We're also using Jasmine to unit-testing the JavaScript files. All the specs are +written in CoffeeScript for the consistency. Because of the limitation of +`django-jasmine` plugin, we'll need to also running another compiler to compile +the test file. + +Using this command to compile the test files: + + coffee -cw templates/coffee/spec/*.coffee + +Then start the server in debug mode, navigate to http://127.0.0.1:8000/_jasmine +to see the test result. diff --git a/urls.py b/urls.py index bbbc892abf..6eda9953d5 100644 --- a/urls.py +++ b/urls.py @@ -77,6 +77,10 @@ if settings.ASKBOT_ENABLED: # url(r'^robots.txt$', include('robots.urls')), ) +if settings.DEBUG: + ## Jasmine + urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),) + urlpatterns = patterns(*urlpatterns) if settings.DEBUG: From 0ab9916017f9372b86615189a5a3bbe283f1e0ad Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Fri, 27 Apr 2012 17:38:58 -0400 Subject: [PATCH 2/3] Rewrite JavaScript on main page Both feedback form and calculator interaction now in its own class. --- static/js/application.js | 75 +++++++++++++++++ templates/coffee/files.json | 8 ++ templates/coffee/fixtures/calculator.html | 18 +++++ templates/coffee/fixtures/feedback_form.html | 7 ++ templates/coffee/spec/calculator_spec.coffee | 68 ++++++++++++++++ templates/coffee/spec/calculator_spec.js | 80 +++++++++++++++++++ .../coffee/spec/feedback_form_spec.coffee | 28 +++++++ templates/coffee/spec/feedback_form_spec.js | 35 ++++++++ templates/coffee/spec/helper.coffee | 1 + templates/coffee/spec/helper.js | 6 ++ templates/coffee/src/calculator.coffee | 20 +++++ templates/coffee/src/feedback_form.coffee | 10 +++ templates/coffee/src/main.coffee | 7 ++ templates/main.html | 72 +++-------------- 14 files changed, 376 insertions(+), 59 deletions(-) create mode 100644 static/js/application.js create mode 100644 templates/coffee/files.json create mode 100644 templates/coffee/fixtures/calculator.html create mode 100644 templates/coffee/fixtures/feedback_form.html create mode 100644 templates/coffee/spec/calculator_spec.coffee create mode 100644 templates/coffee/spec/calculator_spec.js create mode 100644 templates/coffee/spec/feedback_form_spec.coffee create mode 100644 templates/coffee/spec/feedback_form_spec.js create mode 100644 templates/coffee/spec/helper.coffee create mode 100644 templates/coffee/spec/helper.js create mode 100644 templates/coffee/src/calculator.coffee create mode 100644 templates/coffee/src/feedback_form.coffee create mode 100644 templates/coffee/src/main.coffee diff --git a/static/js/application.js b/static/js/application.js new file mode 100644 index 0000000000..1591b6fc44 --- /dev/null +++ b/static/js/application.js @@ -0,0 +1,75 @@ +// Generated by CoffeeScript 1.3.2-pre +(function() { + + window.Calculator = (function() { + + function Calculator() {} + + Calculator.bind = function() { + var calculator; + calculator = new Calculator; + $('.calc').click(calculator.toggle); + $('form#calculator').submit(calculator.calculate).submit(function(e) { + return e.preventDefault(); + }); + return $('div.help-wrapper a').hover(calculator.helpToggle).click(function(e) { + return e.preventDefault(); + }); + }; + + Calculator.prototype.toggle = function() { + $('li.calc-main').toggleClass('open'); + $('#calculator_wrapper #calculator_input').focus(); + return $('.calc').toggleClass('closed'); + }; + + Calculator.prototype.helpToggle = function() { + return $('.help').toggleClass('shown'); + }; + + Calculator.prototype.calculate = function() { + return $.getJSON('/calculate', { + equation: $('#calculator_input').val() + }, function(data) { + return $('#calculator_output').val(data.result); + }); + }; + + return Calculator; + + })(); + + window.FeedbackForm = (function() { + + function FeedbackForm() {} + + FeedbackForm.bind = function() { + return $('#feedback_button').click(function() { + var data; + data = { + subject: $('#feedback_subject').val(), + message: $('#feedback_message').val(), + url: window.location.href + }; + return $.post('/send_feedback', data, function() { + return $('#feedback_div').html('Feedback submitted. Thank you'); + }, 'json'); + }); + }; + + return FeedbackForm; + + })(); + + $(function() { + $.ajaxSetup({ + headers: { + 'X-CSRFToken': $.cookie('csrftoken') + } + }); + FeedbackForm.bind(); + Calculator.bind(); + return $("a[rel*=leanModal]").leanModal(); + }); + +}).call(this); diff --git a/templates/coffee/files.json b/templates/coffee/files.json new file mode 100644 index 0000000000..44494e8040 --- /dev/null +++ b/templates/coffee/files.json @@ -0,0 +1,8 @@ +{ + "js_files": [ + "/static/js/jquery-1.6.2.min.js" + ], + "static_files": [ + "js/application.js" + ] +} diff --git a/templates/coffee/fixtures/calculator.html b/templates/coffee/fixtures/calculator.html new file mode 100644 index 0000000000..61c6f5e153 --- /dev/null +++ b/templates/coffee/fixtures/calculator.html @@ -0,0 +1,18 @@ + diff --git a/templates/coffee/fixtures/feedback_form.html b/templates/coffee/fixtures/feedback_form.html new file mode 100644 index 0000000000..672663fe10 --- /dev/null +++ b/templates/coffee/fixtures/feedback_form.html @@ -0,0 +1,7 @@ +
+
+ + + +
+
diff --git a/templates/coffee/spec/calculator_spec.coffee b/templates/coffee/spec/calculator_spec.coffee new file mode 100644 index 0000000000..5c3fde5e2d --- /dev/null +++ b/templates/coffee/spec/calculator_spec.coffee @@ -0,0 +1,68 @@ +describe 'Calculator', -> + beforeEach -> + loadFixtures 'calculator.html' + @calculator = new Calculator + + describe 'bind', -> + beforeEach -> + Calculator.bind() + + it 'bind the calculator button', -> + expect($('.calc')).toHandleWith 'click', @calculator.toggle + + it 'bind the help button', -> + # These events are bind by $.hover() + expect($('div.help-wrapper a')).toHandleWith 'mouseenter', @calculator.helpToggle + expect($('div.help-wrapper a')).toHandleWith 'mouseleave', @calculator.helpToggle + + it 'prevent default behavior on help button', -> + $('div.help-wrapper a').click (e) -> + expect(e.isDefaultPrevented()).toBeTruthy() + $('div.help-wrapper a').click() + + it 'bind the calculator submit', -> + expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate + + it 'prevent default behavior on form submit', -> + $('form#calculator').submit (e) -> + expect(e.isDefaultPrevented()).toBeTruthy() + e.preventDefault() + $('form#calculator').submit() + + describe 'toggle', -> + it 'toggle the calculator and focus the input', -> + spyOn $.fn, 'focus' + @calculator.toggle() + + expect($('li.calc-main')).toHaveClass('open') + expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled() + + it 'toggle the close button on the calculator button', -> + @calculator.toggle() + expect($('.calc')).toHaveClass('closed') + + @calculator.toggle() + expect($('.calc')).not.toHaveClass('closed') + + describe 'helpToggle', -> + it 'toggle the help overlay', -> + @calculator.helpToggle() + expect($('.help')).toHaveClass('shown') + + @calculator.helpToggle() + expect($('.help')).not.toHaveClass('shown') + + describe 'calculate', -> + beforeEach -> + $('#calculator_input').val '1+2' + spyOn($, 'getJSON').andCallFake (url, data, callback) -> + callback({ result: 3 }) + @calculator.calculate() + + it 'send data to /calculate', -> + expect($.getJSON).toHaveBeenCalledWith '/calculate', + equation: '1+2' + , jasmine.any(Function) + + it 'update the calculator output', -> + expect($('#calculator_output').val()).toEqual('3') diff --git a/templates/coffee/spec/calculator_spec.js b/templates/coffee/spec/calculator_spec.js new file mode 100644 index 0000000000..6e0f8a0dab --- /dev/null +++ b/templates/coffee/spec/calculator_spec.js @@ -0,0 +1,80 @@ +// Generated by CoffeeScript 1.3.2-pre +(function() { + + describe('Calculator', function() { + beforeEach(function() { + loadFixtures('calculator.html'); + return this.calculator = new Calculator; + }); + describe('bind', function() { + beforeEach(function() { + return Calculator.bind(); + }); + it('bind the calculator button', function() { + return expect($('.calc')).toHandleWith('click', this.calculator.toggle); + }); + it('bind the help button', function() { + expect($('div.help-wrapper a')).toHandleWith('mouseenter', this.calculator.helpToggle); + return expect($('div.help-wrapper a')).toHandleWith('mouseleave', this.calculator.helpToggle); + }); + it('prevent default behavior on help button', function() { + $('div.help-wrapper a').click(function(e) { + return expect(e.isDefaultPrevented()).toBeTruthy(); + }); + return $('div.help-wrapper a').click(); + }); + it('bind the calculator submit', function() { + return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate); + }); + return it('prevent default behavior on form submit', function() { + $('form#calculator').submit(function(e) { + expect(e.isDefaultPrevented()).toBeTruthy(); + return e.preventDefault(); + }); + return $('form#calculator').submit(); + }); + }); + describe('toggle', function() { + it('toggle the calculator and focus the input', function() { + spyOn($.fn, 'focus'); + this.calculator.toggle(); + expect($('li.calc-main')).toHaveClass('open'); + return expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled(); + }); + return it('toggle the close button on the calculator button', function() { + this.calculator.toggle(); + expect($('.calc')).toHaveClass('closed'); + this.calculator.toggle(); + return expect($('.calc')).not.toHaveClass('closed'); + }); + }); + describe('helpToggle', function() { + return it('toggle the help overlay', function() { + this.calculator.helpToggle(); + expect($('.help')).toHaveClass('shown'); + this.calculator.helpToggle(); + return expect($('.help')).not.toHaveClass('shown'); + }); + }); + return describe('calculate', function() { + beforeEach(function() { + $('#calculator_input').val('1+2'); + spyOn($, 'getJSON').andCallFake(function(url, data, callback) { + return callback({ + result: 3 + }); + }); + return this.calculator.calculate(); + }); + it('send data to /calculate', function() { + return expect($.getJSON).toHaveBeenCalledWith('/calculate', { + equation: '1+2' + }, jasmine.any(Function)); + }); + return it('update the calculator output', function() { + return expect($('#calculator_output').val()).toEqual('3'); + }); + }); + }); + +}).call(this); diff --git a/templates/coffee/spec/feedback_form_spec.coffee b/templates/coffee/spec/feedback_form_spec.coffee new file mode 100644 index 0000000000..191645b3d3 --- /dev/null +++ b/templates/coffee/spec/feedback_form_spec.coffee @@ -0,0 +1,28 @@ +describe 'FeedbackForm', -> + beforeEach -> + loadFixtures 'feedback_form.html' + + describe 'bind', -> + beforeEach -> + FeedbackForm.bind() + spyOn($, 'post').andCallFake (url, data, callback, format) -> + callback() + + it 'binds to the #feedback_button', -> + expect($('#feedback_button')).toHandle 'click' + + it 'post data to /send_feedback on click', -> + $('#feedback_subject').val 'Awesome!' + $('#feedback_message').val 'This site is really good.' + $('#feedback_button').click() + + expect($.post).toHaveBeenCalledWith '/send_feedback', { + subject: 'Awesome!' + message: 'This site is really good.' + url: window.location.href + }, jasmine.any(Function), 'json' + + it 'replace the form with a thank you message', -> + $('#feedback_button').click() + + expect($('#feedback_div').html()).toEqual 'Feedback submitted. Thank you' diff --git a/templates/coffee/spec/feedback_form_spec.js b/templates/coffee/spec/feedback_form_spec.js new file mode 100644 index 0000000000..2815cd73e5 --- /dev/null +++ b/templates/coffee/spec/feedback_form_spec.js @@ -0,0 +1,35 @@ +// Generated by CoffeeScript 1.3.2-pre +(function() { + + describe('FeedbackForm', function() { + beforeEach(function() { + return loadFixtures('feedback_form.html'); + }); + return describe('bind', function() { + beforeEach(function() { + FeedbackForm.bind(); + return spyOn($, 'post').andCallFake(function(url, data, callback, format) { + return callback(); + }); + }); + it('binds to the #feedback_button', function() { + return expect($('#feedback_button')).toHandle('click'); + }); + it('post data to /send_feedback on click', function() { + $('#feedback_subject').val('Awesome!'); + $('#feedback_message').val('This site is really good.'); + $('#feedback_button').click(); + return expect($.post).toHaveBeenCalledWith('/send_feedback', { + subject: 'Awesome!', + message: 'This site is really good.', + url: window.location.href + }, jasmine.any(Function), 'json'); + }); + return it('replace the form with a thank you message', function() { + $('#feedback_button').click(); + return expect($('#feedback_div').html()).toEqual('Feedback submitted. Thank you'); + }); + }); + }); + +}).call(this); diff --git a/templates/coffee/spec/helper.coffee b/templates/coffee/spec/helper.coffee new file mode 100644 index 0000000000..1f27e257c2 --- /dev/null +++ b/templates/coffee/spec/helper.coffee @@ -0,0 +1 @@ +jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/" diff --git a/templates/coffee/spec/helper.js b/templates/coffee/spec/helper.js new file mode 100644 index 0000000000..23b980d525 --- /dev/null +++ b/templates/coffee/spec/helper.js @@ -0,0 +1,6 @@ +// Generated by CoffeeScript 1.3.2-pre +(function() { + + jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/"; + +}).call(this); diff --git a/templates/coffee/src/calculator.coffee b/templates/coffee/src/calculator.coffee new file mode 100644 index 0000000000..7d62f5a794 --- /dev/null +++ b/templates/coffee/src/calculator.coffee @@ -0,0 +1,20 @@ +class window.Calculator + @bind: -> + calculator = new Calculator + $('.calc').click calculator.toggle + $('form#calculator').submit(calculator.calculate).submit (e) -> + e.preventDefault() + $('div.help-wrapper a').hover(calculator.helpToggle).click (e) -> + e.preventDefault() + + toggle: -> + $('li.calc-main').toggleClass 'open' + $('#calculator_wrapper #calculator_input').focus() + $('.calc').toggleClass 'closed' + + helpToggle: -> + $('.help').toggleClass 'shown' + + calculate: -> + $.getJSON '/calculate', { equation: $('#calculator_input').val() }, (data) -> + $('#calculator_output').val(data.result) diff --git a/templates/coffee/src/feedback_form.coffee b/templates/coffee/src/feedback_form.coffee new file mode 100644 index 0000000000..bbb5c09365 --- /dev/null +++ b/templates/coffee/src/feedback_form.coffee @@ -0,0 +1,10 @@ +class window.FeedbackForm + @bind: -> + $('#feedback_button').click -> + data = + subject: $('#feedback_subject').val() + message: $('#feedback_message').val() + url: window.location.href + $.post '/send_feedback', data, -> + $('#feedback_div').html 'Feedback submitted. Thank you' + ,'json' diff --git a/templates/coffee/src/main.coffee b/templates/coffee/src/main.coffee new file mode 100644 index 0000000000..dc8c4a0622 --- /dev/null +++ b/templates/coffee/src/main.coffee @@ -0,0 +1,7 @@ +$ -> + $.ajaxSetup + headers : { 'X-CSRFToken': $.cookie 'csrftoken' } + + Calculator.bind() + FeedbackForm.bind() + $("a[rel*=leanModal]").leanModal() diff --git a/templates/main.html b/templates/main.html index 7078ed64d3..59a1ed7bd0 100644 --- a/templates/main.html +++ b/templates/main.html @@ -9,6 +9,7 @@ +