oojs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.gitmodules ADDED
@@ -0,0 +1,9 @@
1
+ [submodule "resources/js-modules"]
2
+ path = resources/js-modules
3
+ url = git://github.com/rosenfeld/js-modules.git
4
+ [submodule "resources/jquery-bbq"]
5
+ path = resources/jquery-bbq
6
+ url = git://github.com/cowboy/jquery-bbq.git
7
+ [submodule "resources/jasmine-jquery"]
8
+ path = resources/jasmine-jquery
9
+ url = git://github.com/velesin/jasmine-jquery.git
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in oojs.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Rodrigo Rosenfeld Rosas
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Object-oriented JavaScript (or CoffeeScript)
2
+
3
+ This is a bundle of tools for helping you to create well organized client-side
4
+ code to be run in browsers covered by tests (or specs) in an easy way.
5
+
6
+ It uses:
7
+
8
+ - [Jasmine](http://pivotal.github.com/jasmine/) for writing specs;
9
+ - [jQuery](http://jquery.com/), the excelent DOM manipulation library;
10
+ - [FakeAjaxServer](https://github.com/rosenfeld/fake-ajax-server) for
11
+ simulating AJAX requests made with jQuery. It uses
12
+ [Sinon.js](http://sinonjs.org/) for stubbing _jQuery.ajax()_ calls;
13
+ - [js-modules](https://github.com/rosenfeld/js-modules) for splitting
14
+ your spec "classes" in multiple files for better maintainance;
15
+ - [Jasmine-jQuery](https://github.com/velesin/jasmine-jquery) for
16
+ adding some useful matchers to Jasmine;
17
+ - [jQuery BBQ](https://github.com/cowboy/jquery-bbq/):
18
+ don't count on that. Currently it is there only to provide $.deparam,
19
+ for helping testing params processed by plugins, like
20
+ [jQuery form](http://jquery.malsup.com/form/). If you don't need
21
+ it, just don't include it;
22
+ - The [Rails Asset Pipeline](http://guides.rubyonrails.org/asset_pipeline.html).
23
+ Yeah, sorry if you don't use Rails, but
24
+ (this can still be helpful)[https://github.com/rosenfeld/jasmine_assets_enabler]
25
+ for non-Rails applications. Just append this gem to that Gemfile (or replace
26
+ rails\_sandbox\_jasmine by oojs).
27
+
28
+ ## Installation
29
+
30
+ Add this line to your application's Gemfile:
31
+
32
+ gem 'oojs'
33
+
34
+ And then execute:
35
+
36
+ $ bundle
37
+
38
+ ## Usage
39
+
40
+ I intend to write an article about it soon.
41
+
42
+ For now, you'll need to create your spec helper first:
43
+
44
+ rails g oojs:spec_helper
45
+
46
+ Then, create some spec:
47
+
48
+ rails g oojs:spec shopping_cart
49
+
50
+ Finally, run your specs:
51
+
52
+ rake sandbox_assets:serve
53
+
54
+ And navigate to http://localhost:5000/ to see them passing.
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Create a sample spec.
3
+
4
+ Example:
5
+ rails generate oojs:spec shopping_cart
6
+
7
+ This will create:
8
+ spec/javascripts/shopping_cart_spec.js.coffee
9
+ spec/javascripts/shopping_cart/fake_server.js.coffee
@@ -0,0 +1,26 @@
1
+ module Oojs
2
+ module Generators
3
+ class SpecGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ argument :spec_name, :type => :string
6
+
7
+ def create_spec
8
+ template 'spec.js.coffee.erb', "spec/javascripts/#{file_name}_spec.js.coffee"
9
+ end
10
+
11
+ def create_fake_ajax_server
12
+ template 'fake_ajax_server.js.coffee.erb', "spec/javascripts/#{file_name}/fake_server.js.coffee"
13
+ end
14
+
15
+ private
16
+
17
+ def file_name
18
+ @file_name ||= spec_name.underscore
19
+ end
20
+
21
+ def class_name
22
+ @class_name ||= spec_name.camelize
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # =require fake_ajax_server
2
+
3
+ createProducts = -> [
4
+ {id: 1, name: 'One'}
5
+ {id: 2, name: 'Two'}
6
+ ]
7
+
8
+ extendClass 'specs.<%= class_name %>Spec', ->
9
+ createFakeServer: ->
10
+ @fakeServer = new FakeAjaxServer (url, settings)->
11
+ if settings then settings.url = url else settings = url
12
+ handled = false
13
+ switch settings.dataType
14
+ when 'json' then switch settings.type
15
+ when 'get' then switch settings.url
16
+ when '/products' then handled = true; settings.success createProducts()
17
+ # when 'post' then switch settings.url
18
+ # when ...
19
+ # when undefined then switch settings.type
20
+ # when 'get' then switch settings.url
21
+ # when ...
22
+ # when 'post' then switch settings.url
23
+ # when ...
24
+ return if handled
25
+ console.log arguments
26
+ throw "Unexpected AJAX call: #{settings.url}"
@@ -0,0 +1,28 @@
1
+ # =require spec_helper
2
+ # =require_tree ./<%= file_name %>
3
+
4
+ $ -> new specs.<%= class_name %>Spec
5
+ extendClass 'specs.<%= class_name %>Spec', (spec)->
6
+ initialize: ->
7
+ @createFakeServer()
8
+ @describe()
9
+
10
+ describe: ->
11
+ describe '<%= class_name %>', =>
12
+ @beforeAll()
13
+ beforeEach @beforeEach
14
+ @runSpecs()
15
+ @afterAll()
16
+
17
+ beforeAll: ->
18
+ @fakeServer.start()
19
+
20
+ afterAll: ->
21
+ @fakeServer.stop()
22
+
23
+ beforeEach: =>
24
+ @fakeServer.ignoreAllRequests()
25
+
26
+ runSpecs: =>
27
+ it 'passes', =>
28
+ expect(@fakeServer).not.toBeUndefined()
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Create a sample spec helper.
3
+
4
+ Example:
5
+ rails generate oojs:spec_helper
6
+
7
+ This will create:
8
+ spec/javascripts/spec_helper.js.coffee
@@ -0,0 +1,11 @@
1
+ module Oojs
2
+ module Generators
3
+ class SpecHelperGenerator < Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ def create_spec_helper
7
+ copy_file 'spec_helper.js.coffee', 'spec/javascripts/spec_helper.js.coffee'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # =require application
2
+ # =require modules
3
+ # =require jquery
4
+ # =require jasmine-jquery
5
+ # #require jquery.ba-bbq # uncomment for enabling $.deparam()
6
+ #
7
+ # Put your common spec code here.
8
+ # Then put "# =require spec_helper" in your specs headers.
data/lib/oojs.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "oojs/version"
2
+ require 'rails_sandbox_jasmine'
3
+ require 'fake-ajax-server'
4
+ require 'sinon-rails'
5
+
6
+ module Oojs
7
+ class Engine < Rails::Engine
8
+ config.sandbox_assets.template ||= 'jasmine/runner'
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Oojs
2
+ VERSION = "0.0.1"
3
+ end
data/oojs.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/oojs/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Rodrigo Rosenfeld Rosas"]
6
+ gem.email = ["rr.rosas@gmail.com"]
7
+ gem.summary = %q{Object-oriented JavaScript (or CoffeeScript) for Rails}
8
+ gem.description = %q{This is a bundle of tools for helping you
9
+ to create well organized client-side
10
+ code to be run in browsers covered by tests (or specs) in an easy way.}
11
+ gem.homepage = "http://github.com/rosenfeld/oojs"
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "oojs"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Oojs::VERSION
19
+
20
+ gem.add_dependency 'rails_sandbox_jasmine'
21
+ gem.add_dependency 'fake-ajax-server'
22
+ end
@@ -0,0 +1,315 @@
1
+ var readFixtures = function() {
2
+ return jasmine.getFixtures().proxyCallTo_('read', arguments);
3
+ };
4
+
5
+ var preloadFixtures = function() {
6
+ jasmine.getFixtures().proxyCallTo_('preload', arguments);
7
+ };
8
+
9
+ var loadFixtures = function() {
10
+ jasmine.getFixtures().proxyCallTo_('load', arguments);
11
+ };
12
+
13
+ var setFixtures = function(html) {
14
+ jasmine.getFixtures().set(html);
15
+ };
16
+
17
+ var sandbox = function(attributes) {
18
+ return jasmine.getFixtures().sandbox(attributes);
19
+ };
20
+
21
+ var spyOnEvent = function(selector, eventName) {
22
+ jasmine.JQuery.events.spyOn(selector, eventName);
23
+ };
24
+
25
+ jasmine.getFixtures = function() {
26
+ return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
27
+ };
28
+
29
+ jasmine.Fixtures = function() {
30
+ this.containerId = 'jasmine-fixtures';
31
+ this.fixturesCache_ = {};
32
+ this.fixturesPath = 'spec/javascripts/fixtures';
33
+ };
34
+
35
+ jasmine.Fixtures.prototype.set = function(html) {
36
+ this.cleanUp();
37
+ this.createContainer_(html);
38
+ };
39
+
40
+ jasmine.Fixtures.prototype.preload = function() {
41
+ this.read.apply(this, arguments);
42
+ };
43
+
44
+ jasmine.Fixtures.prototype.load = function() {
45
+ this.cleanUp();
46
+ this.createContainer_(this.read.apply(this, arguments));
47
+ };
48
+
49
+ jasmine.Fixtures.prototype.read = function() {
50
+ var htmlChunks = [];
51
+
52
+ var fixtureUrls = arguments;
53
+ for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
54
+ htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
55
+ }
56
+
57
+ return htmlChunks.join('');
58
+ };
59
+
60
+ jasmine.Fixtures.prototype.clearCache = function() {
61
+ this.fixturesCache_ = {};
62
+ };
63
+
64
+ jasmine.Fixtures.prototype.cleanUp = function() {
65
+ jQuery('#' + this.containerId).remove();
66
+ };
67
+
68
+ jasmine.Fixtures.prototype.sandbox = function(attributes) {
69
+ var attributesToSet = attributes || {};
70
+ return jQuery('<div id="sandbox" />').attr(attributesToSet);
71
+ };
72
+
73
+ jasmine.Fixtures.prototype.createContainer_ = function(html) {
74
+ var container;
75
+ if(html instanceof jQuery) {
76
+ container = jQuery('<div id="' + this.containerId + '" />');
77
+ container.html(html);
78
+ } else {
79
+ container = '<div id="' + this.containerId + '">' + html + '</div>'
80
+ }
81
+ jQuery('body').append(container);
82
+ };
83
+
84
+ jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
85
+ if (typeof this.fixturesCache_[url] == 'undefined') {
86
+ this.loadFixtureIntoCache_(url);
87
+ }
88
+ return this.fixturesCache_[url];
89
+ };
90
+
91
+ jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
92
+ var url = this.makeFixtureUrl_(relativeUrl);
93
+ var request = new XMLHttpRequest();
94
+ request.open("GET", url + "?" + new Date().getTime(), false);
95
+ request.send(null);
96
+ this.fixturesCache_[relativeUrl] = request.responseText;
97
+ };
98
+
99
+ jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){
100
+ return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
101
+ };
102
+
103
+ jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
104
+ return this[methodName].apply(this, passedArguments);
105
+ };
106
+
107
+
108
+ jasmine.JQuery = function() {};
109
+
110
+ jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
111
+ return jQuery('<div/>').append(html).html();
112
+ };
113
+
114
+ jasmine.JQuery.elementToString = function(element) {
115
+ var domEl = $(element).get(0)
116
+ if (domEl == undefined || domEl.cloneNode)
117
+ return jQuery('<div />').append($(element).clone()).html();
118
+ else
119
+ return element.toString();
120
+ };
121
+
122
+ jasmine.JQuery.matchersClass = {};
123
+
124
+ (function(namespace) {
125
+ var data = {
126
+ spiedEvents: {},
127
+ handlers: []
128
+ };
129
+
130
+ namespace.events = {
131
+ spyOn: function(selector, eventName) {
132
+ var handler = function(e) {
133
+ data.spiedEvents[[selector, eventName]] = e;
134
+ };
135
+ jQuery(selector).bind(eventName, handler);
136
+ data.handlers.push(handler);
137
+ },
138
+
139
+ wasTriggered: function(selector, eventName) {
140
+ return !!(data.spiedEvents[[selector, eventName]]);
141
+ },
142
+
143
+ wasPrevented: function(selector, eventName) {
144
+ return data.spiedEvents[[selector, eventName]].isDefaultPrevented();
145
+ },
146
+
147
+ cleanUp: function() {
148
+ data.spiedEvents = {};
149
+ data.handlers = [];
150
+ }
151
+ }
152
+ })(jasmine.JQuery);
153
+
154
+ (function(){
155
+ var jQueryMatchers = {
156
+ toHaveClass: function(className) {
157
+ return this.actual.hasClass(className);
158
+ },
159
+
160
+ toBeVisible: function() {
161
+ return this.actual.is(':visible');
162
+ },
163
+
164
+ toBeHidden: function() {
165
+ return this.actual.is(':hidden');
166
+ },
167
+
168
+ toBeSelected: function() {
169
+ return this.actual.is(':selected');
170
+ },
171
+
172
+ toBeChecked: function() {
173
+ return this.actual.is(':checked');
174
+ },
175
+
176
+ toBeEmpty: function() {
177
+ return this.actual.is(':empty');
178
+ },
179
+
180
+ toExist: function() {
181
+ return $(document).find(this.actual).length;
182
+ },
183
+
184
+ toHaveAttr: function(attributeName, expectedAttributeValue) {
185
+ return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
186
+ },
187
+
188
+ toHaveProp: function(propertyName, expectedPropertyValue) {
189
+ return hasProperty(this.actual.prop(propertyName), expectedPropertyValue);
190
+ },
191
+
192
+ toHaveId: function(id) {
193
+ return this.actual.attr('id') == id;
194
+ },
195
+
196
+ toHaveHtml: function(html) {
197
+ return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
198
+ },
199
+
200
+ toHaveText: function(text) {
201
+ var trimmedText = $.trim(this.actual.text());
202
+ if (text && jQuery.isFunction(text.test)) {
203
+ return text.test(trimmedText);
204
+ } else {
205
+ return trimmedText == text;
206
+ }
207
+ },
208
+
209
+ toHaveValue: function(value) {
210
+ return this.actual.val() == value;
211
+ },
212
+
213
+ toHaveData: function(key, expectedValue) {
214
+ return hasProperty(this.actual.data(key), expectedValue);
215
+ },
216
+
217
+ toBe: function(selector) {
218
+ return this.actual.is(selector);
219
+ },
220
+
221
+ toContain: function(selector) {
222
+ return this.actual.find(selector).length;
223
+ },
224
+
225
+ toBeDisabled: function(selector){
226
+ return this.actual.is(':disabled');
227
+ },
228
+
229
+ toBeFocused: function(selector) {
230
+ return this.actual.is(':focus');
231
+ },
232
+
233
+ // tests the existence of a specific event binding
234
+ toHandle: function(eventName) {
235
+ var events = this.actual.data("events");
236
+ return events && events[eventName].length > 0;
237
+ },
238
+
239
+ // tests the existence of a specific event binding + handler
240
+ toHandleWith: function(eventName, eventHandler) {
241
+ var stack = this.actual.data("events")[eventName];
242
+ var i;
243
+ for (i = 0; i < stack.length; i++) {
244
+ if (stack[i].handler == eventHandler) {
245
+ return true;
246
+ }
247
+ }
248
+ return false;
249
+ }
250
+ };
251
+
252
+ var hasProperty = function(actualValue, expectedValue) {
253
+ if (expectedValue === undefined) {
254
+ return actualValue !== undefined;
255
+ }
256
+ return actualValue == expectedValue;
257
+ };
258
+
259
+ var bindMatcher = function(methodName) {
260
+ var builtInMatcher = jasmine.Matchers.prototype[methodName];
261
+
262
+ jasmine.JQuery.matchersClass[methodName] = function() {
263
+ if (this.actual
264
+ && (this.actual instanceof jQuery
265
+ || jasmine.isDomNode(this.actual))) {
266
+ this.actual = $(this.actual);
267
+ var result = jQueryMatchers[methodName].apply(this, arguments)
268
+ if (this.actual.get && !$.isWindow(this.actual.get()[0]))
269
+ this.actual = jasmine.JQuery.elementToString(this.actual)
270
+ return result;
271
+ }
272
+
273
+ if (builtInMatcher) {
274
+ return builtInMatcher.apply(this, arguments);
275
+ }
276
+
277
+ return false;
278
+ };
279
+ };
280
+
281
+ for(var methodName in jQueryMatchers) {
282
+ bindMatcher(methodName);
283
+ }
284
+ })();
285
+
286
+ beforeEach(function() {
287
+ this.addMatchers(jasmine.JQuery.matchersClass);
288
+ this.addMatchers({
289
+ toHaveBeenTriggeredOn: function(selector) {
290
+ this.message = function() {
291
+ return [
292
+ "Expected event " + this.actual + " to have been triggered on " + selector,
293
+ "Expected event " + this.actual + " not to have been triggered on " + selector
294
+ ];
295
+ };
296
+ return jasmine.JQuery.events.wasTriggered($(selector), this.actual);
297
+ }
298
+ });
299
+ this.addMatchers({
300
+ toHaveBeenPreventedOn: function(selector) {
301
+ this.message = function() {
302
+ return [
303
+ "Expected event " + this.actual + " to have been prevented on " + selector,
304
+ "Expected event " + this.actual + " not to have been prevented on " + selector
305
+ ];
306
+ };
307
+ return jasmine.JQuery.events.wasPrevented(selector, this.actual);
308
+ }
309
+ });
310
+ });
311
+
312
+ afterEach(function() {
313
+ jasmine.getFixtures().cleanUp();
314
+ jasmine.JQuery.events.cleanUp();
315
+ });