modularity-rails 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +8 -5
- data/Guardfile +9 -0
- data/README.md +60 -11
- data/lib/modularity-rails/version.rb +1 -1
- data/modularity-rails.gemspec +2 -0
- data/spec/javascripts/external/jasmine-jquery.js +288 -0
- data/spec/javascripts/mixins/closable_spec.coffee +41 -0
- data/spec/javascripts/modularity_spec.coffee +17 -26
- data/spec/javascripts/modules/button_spec.coffee +45 -0
- data/spec/javascripts/modules/counter_button_spec.coffee +64 -0
- data/spec/javascripts/spec_helper.coffee +22 -0
- data/spec/javascripts/templates/button.html +3 -0
- data/spec/javascripts/templates/closable.html +10 -0
- data/vendor/assets/javascripts/mixins/clickable.coffee +21 -0
- data/vendor/assets/javascripts/mixins/closable.coffee +18 -0
- data/vendor/assets/javascripts/modularity.js.coffee +9 -7
- data/vendor/assets/javascripts/modules/button.coffee +6 -0
- data/vendor/assets/javascripts/modules/counter_button.coffee +15 -0
- metadata +42 -8
data/.gitignore
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
*.gem
|
2
|
-
*.rbc
|
3
1
|
.bundle
|
4
2
|
.config
|
3
|
+
.DS_Store
|
5
4
|
.yardoc
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
*.gem
|
6
|
+
*.rbc
|
7
|
+
*.swp
|
8
|
+
*.swo
|
9
9
|
coverage
|
10
10
|
doc/
|
11
|
+
Gemfile.lock
|
12
|
+
InstalledFiles
|
11
13
|
lib/bundler/man
|
12
14
|
pkg
|
13
15
|
rdoc
|
@@ -15,3 +17,4 @@ spec/reports
|
|
15
17
|
test/tmp
|
16
18
|
test/version_tmp
|
17
19
|
tmp
|
20
|
+
_yardoc
|
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'livereload' do
|
5
|
+
watch(%r{spec/javascripts/.+})
|
6
|
+
watch(%r{vendor/assets/javascripts/.+})
|
7
|
+
# Rails Assets Pipeline
|
8
|
+
# watch(%r{(app|vendor)/assets/\w+/(.+\.(css|js|html)).*}) { |m| "/assets/#{m[2]}" }
|
9
|
+
end
|
data/README.md
CHANGED
@@ -3,8 +3,12 @@
|
|
3
3
|
Makes the [Modularity CoffeeScript](http://github.com/kevgo/modularity-coffeescript) library available to
|
4
4
|
Rails 3.1 applications.
|
5
5
|
|
6
|
+
Modularity is a pattern and framework for lightweight object-oriented JavaScript
|
7
|
+
that allows to compose functionally rich web pages in a clean and testable way
|
8
|
+
out of well structured and reusable components.
|
6
9
|
|
7
|
-
|
10
|
+
|
11
|
+
# Installation
|
8
12
|
|
9
13
|
Add this line to your application's Gemfile:
|
10
14
|
|
@@ -19,25 +23,39 @@ $ bundle
|
|
19
23
|
```
|
20
24
|
|
21
25
|
Finally, you have to load the modularity file into your application's javascript.
|
22
|
-
The easiest way is to add it to `application.
|
26
|
+
The easiest way is to add it to `application.coffee`:
|
23
27
|
|
24
|
-
```
|
25
|
-
|
26
|
-
|
27
|
-
*= require modularity
|
28
|
-
*/
|
28
|
+
```coffeescript
|
29
|
+
# require jquery
|
30
|
+
# require modularity
|
29
31
|
```
|
30
32
|
|
31
33
|
|
32
|
-
|
34
|
+
# Usage
|
35
|
+
|
36
|
+
Modularity is a lightweight framework for building powerful AJAX applications.
|
37
|
+
Modularity avoids magic and heavyness. It focusses on providing a pragmatic and interoperable foundation
|
38
|
+
for clean hand-written code bases.
|
39
|
+
Modularity provides practices to create code bases of incredible complexity that are still
|
40
|
+
nicely manageable and perform very well.
|
41
|
+
|
42
|
+
|
43
|
+
# Modules
|
44
|
+
|
45
|
+
Modules are native CoffeeScript classes that are specialized for doing what most JavaScript running in browsers does:
|
46
|
+
managing a UI consisting of DOM elements, reacting to events that happen within that section,
|
47
|
+
representing application logic specific to that section, and providing high-level APIs for others to interact with the section.
|
48
|
+
|
49
|
+
Each module has a container. The container is the outermost DOM element of a section.
|
50
|
+
Everything the module does must happen inside this container.
|
51
|
+
The module is responsible for managing the inner DOM-structure of the container.
|
33
52
|
|
34
|
-
See [http://github.com/kevgo/modularity-coffeescript].
|
35
53
|
|
36
54
|
## Mixins
|
37
55
|
|
38
56
|
Similar to Ruby mixins, mixins in Modularity allow to include orthogonal functional aspects defined in separate objects into a class.
|
39
|
-
```coffeescript
|
40
57
|
|
58
|
+
```coffeescript
|
41
59
|
myMixin =
|
42
60
|
|
43
61
|
# This will be called when an instance of a class that includes this mixin is created.
|
@@ -57,10 +75,28 @@ class MyModule extends Module
|
|
57
75
|
super
|
58
76
|
|
59
77
|
# ...
|
60
|
-
|
61
78
|
```
|
62
79
|
|
63
80
|
|
81
|
+
## Reusable example modules and mixins.
|
82
|
+
|
83
|
+
Modularity comes bundled with a bunch of example modules and mixins that can be used in production code.
|
84
|
+
The example modules are located in `vendor/assets/javascripts/modules/` and _vendor/assets/javascripts/mixins_
|
85
|
+
and must be explicitly required in your Rails files using the `require` commands of the asset pipeline.
|
86
|
+
|
87
|
+
|
88
|
+
### Modules
|
89
|
+
|
90
|
+
* __button.coffee__: A simple button. Fires the `clicked` event when anything inside the container is clicked. Uses the `clickable` mixin.
|
91
|
+
* __counter_button.coffee__: Similar to button, but includes the click count as data payload in the fired event.
|
92
|
+
|
93
|
+
|
94
|
+
### Mixins
|
95
|
+
|
96
|
+
* __clickable.coffee__: Including this mixins adds a 'clickable' aspect to your module, i.e. turns it into a button. Clicking anywhere inside the container makes it fire the 'clicked' event.
|
97
|
+
* __closable.coffee__: Including this mixin makes a module closable. This means that when the user clicks on an embedded DOM element with the class 'CloseButton', a 'closing' event will be fired, and the whole module will be removed from the DOM.
|
98
|
+
|
99
|
+
|
64
100
|
# Development
|
65
101
|
|
66
102
|
## Contributing
|
@@ -77,3 +113,16 @@ class MyModule extends Module
|
|
77
113
|
```bash
|
78
114
|
$ evergreen run
|
79
115
|
```
|
116
|
+
|
117
|
+
|
118
|
+
## Automatically refreshing the browser during development.
|
119
|
+
|
120
|
+
Modularity-Rails comes with support for [LifeReload](https://github.com/mockko/livereload) via [Guard](https://github.com/guard/guard).
|
121
|
+
|
122
|
+
* Install the LiveReload browser extension: [Chrome](https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei)
|
123
|
+
* Run the evergreen server: ```$ evergreen run```
|
124
|
+
* Run the guard server: ``` $ bundle exec guard ```
|
125
|
+
* Start the LiveReload plugin in Chrome (button in address bar).
|
126
|
+
* Navigate to the test page that you want to observe.
|
127
|
+
* Change and save code and see the browser reload.
|
128
|
+
|
data/modularity-rails.gemspec
CHANGED
@@ -13,6 +13,8 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.add_dependency "rails", ">= 3.1.0"
|
14
14
|
s.add_development_dependency "capybara-webkit"
|
15
15
|
s.add_development_dependency "evergreen"
|
16
|
+
s.add_development_dependency "rb-fsevent" if RUBY_PLATFORM =~ /darwin/i
|
17
|
+
s.add_development_dependency "guard-livereload"
|
16
18
|
|
17
19
|
s.files = `git ls-files`.split("\n")
|
18
20
|
s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
|
@@ -0,0 +1,288 @@
|
|
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 self = this;
|
93
|
+
var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
|
94
|
+
jQuery.ajax({
|
95
|
+
async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
|
96
|
+
cache: false,
|
97
|
+
dataType: 'html',
|
98
|
+
url: url,
|
99
|
+
success: function(data) {
|
100
|
+
self.fixturesCache_[relativeUrl] = data;
|
101
|
+
},
|
102
|
+
error: function(jqXHR, status, errorThrown) {
|
103
|
+
throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
|
104
|
+
}
|
105
|
+
});
|
106
|
+
};
|
107
|
+
|
108
|
+
jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
|
109
|
+
return this[methodName].apply(this, passedArguments);
|
110
|
+
};
|
111
|
+
|
112
|
+
|
113
|
+
jasmine.JQuery = function() {};
|
114
|
+
|
115
|
+
jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
|
116
|
+
return jQuery('<div/>').append(html).html();
|
117
|
+
};
|
118
|
+
|
119
|
+
jasmine.JQuery.elementToString = function(element) {
|
120
|
+
return jQuery('<div />').append(element.clone()).html();
|
121
|
+
};
|
122
|
+
|
123
|
+
jasmine.JQuery.matchersClass = {};
|
124
|
+
|
125
|
+
(function(namespace) {
|
126
|
+
var data = {
|
127
|
+
spiedEvents: {},
|
128
|
+
handlers: []
|
129
|
+
};
|
130
|
+
|
131
|
+
namespace.events = {
|
132
|
+
spyOn: function(selector, eventName) {
|
133
|
+
var handler = function(e) {
|
134
|
+
data.spiedEvents[[selector, eventName]] = e;
|
135
|
+
};
|
136
|
+
jQuery(selector).bind(eventName, handler);
|
137
|
+
data.handlers.push(handler);
|
138
|
+
},
|
139
|
+
|
140
|
+
wasTriggered: function(selector, eventName) {
|
141
|
+
return !!(data.spiedEvents[[selector, eventName]]);
|
142
|
+
},
|
143
|
+
|
144
|
+
cleanUp: function() {
|
145
|
+
data.spiedEvents = {};
|
146
|
+
data.handlers = [];
|
147
|
+
}
|
148
|
+
}
|
149
|
+
})(jasmine.JQuery);
|
150
|
+
|
151
|
+
(function(){
|
152
|
+
var jQueryMatchers = {
|
153
|
+
toHaveClass: function(className) {
|
154
|
+
return this.actual.hasClass(className);
|
155
|
+
},
|
156
|
+
|
157
|
+
toBeVisible: function() {
|
158
|
+
return this.actual.is(':visible');
|
159
|
+
},
|
160
|
+
|
161
|
+
toBeHidden: function() {
|
162
|
+
return this.actual.is(':hidden');
|
163
|
+
},
|
164
|
+
|
165
|
+
toBeSelected: function() {
|
166
|
+
return this.actual.is(':selected');
|
167
|
+
},
|
168
|
+
|
169
|
+
toBeChecked: function() {
|
170
|
+
return this.actual.is(':checked');
|
171
|
+
},
|
172
|
+
|
173
|
+
toBeEmpty: function() {
|
174
|
+
return this.actual.is(':empty');
|
175
|
+
},
|
176
|
+
|
177
|
+
toExist: function() {
|
178
|
+
return this.actual.size() > 0;
|
179
|
+
},
|
180
|
+
|
181
|
+
toHaveAttr: function(attributeName, expectedAttributeValue) {
|
182
|
+
return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
|
183
|
+
},
|
184
|
+
|
185
|
+
toHaveId: function(id) {
|
186
|
+
return this.actual.attr('id') == id;
|
187
|
+
},
|
188
|
+
|
189
|
+
toHaveHtml: function(html) {
|
190
|
+
return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
|
191
|
+
},
|
192
|
+
|
193
|
+
toHaveText: function(text) {
|
194
|
+
if (text && jQuery.isFunction(text.test)) {
|
195
|
+
return text.test(this.actual.text());
|
196
|
+
} else {
|
197
|
+
return this.actual.text() == text;
|
198
|
+
}
|
199
|
+
},
|
200
|
+
|
201
|
+
toHaveValue: function(value) {
|
202
|
+
return this.actual.val() == value;
|
203
|
+
},
|
204
|
+
|
205
|
+
toHaveData: function(key, expectedValue) {
|
206
|
+
return hasProperty(this.actual.data(key), expectedValue);
|
207
|
+
},
|
208
|
+
|
209
|
+
toBe: function(selector) {
|
210
|
+
return this.actual.is(selector);
|
211
|
+
},
|
212
|
+
|
213
|
+
toContain: function(selector) {
|
214
|
+
return this.actual.find(selector).size() > 0;
|
215
|
+
},
|
216
|
+
|
217
|
+
toBeDisabled: function(selector){
|
218
|
+
return this.actual.is(':disabled');
|
219
|
+
},
|
220
|
+
|
221
|
+
// tests the existence of a specific event binding
|
222
|
+
toHandle: function(eventName) {
|
223
|
+
var events = this.actual.data("events");
|
224
|
+
return events && events[eventName].length > 0;
|
225
|
+
},
|
226
|
+
|
227
|
+
// tests the existence of a specific event binding + handler
|
228
|
+
toHandleWith: function(eventName, eventHandler) {
|
229
|
+
var stack = this.actual.data("events")[eventName];
|
230
|
+
var i;
|
231
|
+
for (i = 0; i < stack.length; i++) {
|
232
|
+
if (stack[i].handler == eventHandler) {
|
233
|
+
return true;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
return false;
|
237
|
+
}
|
238
|
+
};
|
239
|
+
|
240
|
+
var hasProperty = function(actualValue, expectedValue) {
|
241
|
+
if (expectedValue === undefined) {
|
242
|
+
return actualValue !== undefined;
|
243
|
+
}
|
244
|
+
return actualValue == expectedValue;
|
245
|
+
};
|
246
|
+
|
247
|
+
var bindMatcher = function(methodName) {
|
248
|
+
var builtInMatcher = jasmine.Matchers.prototype[methodName];
|
249
|
+
|
250
|
+
jasmine.JQuery.matchersClass[methodName] = function() {
|
251
|
+
if (this.actual instanceof jQuery) {
|
252
|
+
var result = jQueryMatchers[methodName].apply(this, arguments);
|
253
|
+
this.actual = jasmine.JQuery.elementToString(this.actual);
|
254
|
+
return result;
|
255
|
+
}
|
256
|
+
|
257
|
+
if (builtInMatcher) {
|
258
|
+
return builtInMatcher.apply(this, arguments);
|
259
|
+
}
|
260
|
+
|
261
|
+
return false;
|
262
|
+
};
|
263
|
+
};
|
264
|
+
|
265
|
+
for(var methodName in jQueryMatchers) {
|
266
|
+
bindMatcher(methodName);
|
267
|
+
}
|
268
|
+
})();
|
269
|
+
|
270
|
+
beforeEach(function() {
|
271
|
+
this.addMatchers(jasmine.JQuery.matchersClass);
|
272
|
+
this.addMatchers({
|
273
|
+
toHaveBeenTriggeredOn: function(selector) {
|
274
|
+
this.message = function() {
|
275
|
+
return [
|
276
|
+
"Expected event " + this.actual + " to have been triggered on" + selector,
|
277
|
+
"Expected event " + this.actual + " not to have been triggered on" + selector
|
278
|
+
];
|
279
|
+
};
|
280
|
+
return jasmine.JQuery.events.wasTriggered(selector, this.actual);
|
281
|
+
}
|
282
|
+
})
|
283
|
+
});
|
284
|
+
|
285
|
+
afterEach(function() {
|
286
|
+
jasmine.getFixtures().cleanUp();
|
287
|
+
jasmine.JQuery.events.cleanUp();
|
288
|
+
});
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
|
3
|
+
describe 'test environment setup', ->
|
4
|
+
|
5
|
+
it 'loading libraries', ->
|
6
|
+
load_modularity()
|
7
|
+
loadCS "/vendor/assets/javascripts/mixins/closable.coffee"
|
8
|
+
|
9
|
+
class window.ClosableModuleStandard extends Module
|
10
|
+
@mixin closable
|
11
|
+
|
12
|
+
|
13
|
+
describe 'Closable', ->
|
14
|
+
template 'closable.html'
|
15
|
+
|
16
|
+
beforeEach ->
|
17
|
+
|
18
|
+
describe 'embedded close button', ->
|
19
|
+
it 'finds the close button div automatically', ->
|
20
|
+
new ClosableModuleStandard('#test #closable1')
|
21
|
+
$('#test #closable1 .CloseButton').click()
|
22
|
+
|
23
|
+
expect($('#test #closable1').length).toEqual(0)
|
24
|
+
|
25
|
+
|
26
|
+
describe 'when clicking the close button', ->
|
27
|
+
it 'removes the container', ->
|
28
|
+
new ClosableModuleStandard('#test #closable1')
|
29
|
+
$('#test #closable1 .CloseButton').click()
|
30
|
+
|
31
|
+
expect($('#test #closable1').length).toEqual(0)
|
32
|
+
|
33
|
+
|
34
|
+
it "fires the 'closed' event", ->
|
35
|
+
closable_module = new ClosableModuleStandard('#test #closable1')
|
36
|
+
closable_module.bind_event('closed', (spy = jasmine.createSpy()))
|
37
|
+
|
38
|
+
$('#test #closable1 .CloseButton').click()
|
39
|
+
|
40
|
+
expect(spy).toHaveBeenCalled()
|
41
|
+
expect(spy.callCount).toEqual(1)
|
@@ -1,21 +1,12 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
eval CoffeeScript.compile data
|
11
|
-
callback() if callback
|
12
|
-
|
13
|
-
# Helper method to load the modularity library before the tests.
|
14
|
-
describe 'modularity loader', ->
|
15
|
-
it 'loading Modularity library ...', ->
|
16
|
-
loadCS '/vendor/assets/javascripts/modularity.js.coffee?'+(new Date()).getTime()
|
17
|
-
|
18
|
-
# Test class.
|
1
|
+
#= require spec_helper
|
2
|
+
|
3
|
+
describe 'setting up test environment', ->
|
4
|
+
|
5
|
+
it 'loading libraries', ->
|
6
|
+
load_modularity()
|
7
|
+
|
8
|
+
it 'defining test classes', ->
|
9
|
+
|
19
10
|
class window.TestModule extends Module
|
20
11
|
constructor: (container) ->
|
21
12
|
super
|
@@ -38,7 +29,7 @@ describe 'modularity', ->
|
|
38
29
|
expect(alert).toHaveBeenCalled()
|
39
30
|
|
40
31
|
it 'tries to load the DOM if the given container is a string', ->
|
41
|
-
new TestModule('#module_container')
|
32
|
+
new TestModule('#test #module_container')
|
42
33
|
expect(alert).not.toHaveBeenCalled()
|
43
34
|
|
44
35
|
it 'shows an error if the container is not a jQuery object', ->
|
@@ -46,11 +37,11 @@ describe 'modularity', ->
|
|
46
37
|
expect(alert).toHaveBeenCalled()
|
47
38
|
|
48
39
|
it 'shows an error if the container is an empty jQuery object', ->
|
49
|
-
new TestModule($('.zonk'))
|
40
|
+
new TestModule($('#test .zonk'))
|
50
41
|
expect(alert).toHaveBeenCalled()
|
51
42
|
|
52
43
|
it 'shows an error if the container has more than one elements', ->
|
53
|
-
new TestModule($('.double'))
|
44
|
+
new TestModule($('#test .double'))
|
54
45
|
expect(alert).toHaveBeenCalled()
|
55
46
|
|
56
47
|
it "allows to provide 'testing' in tests", ->
|
@@ -100,11 +91,11 @@ describe 'modularity', ->
|
|
100
91
|
spyOn window, 'alert'
|
101
92
|
|
102
93
|
it 'works', ->
|
103
|
-
$('#module_container').module(TestModule)
|
94
|
+
$('#test #module_container').module(TestModule)
|
104
95
|
expect(alert).not.toHaveBeenCalled()
|
105
96
|
|
106
97
|
it 'returns the created instance', ->
|
107
|
-
result = $('#module_container').module(Module)
|
98
|
+
result = $('#test #module_container').module(Module)
|
108
99
|
expect(result).toBeDefined()
|
109
100
|
expect(typeof result).toEqual('object')
|
110
101
|
|
@@ -113,7 +104,7 @@ describe 'modularity', ->
|
|
113
104
|
expect(alert).toHaveBeenCalled()
|
114
105
|
|
115
106
|
it 'returns an error if the jQuery object is empty.', ->
|
116
|
-
$('#zonk').module(Module)
|
107
|
+
$('#test #zonk').module(Module)
|
117
108
|
expect(alert).toHaveBeenCalled()
|
118
109
|
|
119
110
|
|
@@ -124,7 +115,7 @@ describe 'modularity', ->
|
|
124
115
|
mockContainer = null
|
125
116
|
|
126
117
|
beforeEach ->
|
127
|
-
mockContainer = $('#module_container')
|
118
|
+
mockContainer = $('#test #module_container')
|
128
119
|
module = new TestModule(mockContainer)
|
129
120
|
spyOn window, 'alert'
|
130
121
|
|
@@ -203,7 +194,7 @@ describe 'modularity', ->
|
|
203
194
|
mockGlobalContainer = null
|
204
195
|
|
205
196
|
beforeEach ->
|
206
|
-
mockGlobalContainer = $('#module_container')
|
197
|
+
mockGlobalContainer = $('#test #module_container')
|
207
198
|
spyOn(Module, 'global_event_container').andReturn(mockGlobalContainer)
|
208
199
|
|
209
200
|
describe 'bind_global_event', ->
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
|
3
|
+
describe 'test environment setup', ->
|
4
|
+
|
5
|
+
it 'loading libraries', ->
|
6
|
+
load_modularity()
|
7
|
+
loadCS "/vendor/assets/javascripts/mixins/clickable.coffee"
|
8
|
+
loadCS "/vendor/assets/javascripts/modules/button.coffee"
|
9
|
+
|
10
|
+
|
11
|
+
describe 'Button', ->
|
12
|
+
template 'button.html'
|
13
|
+
|
14
|
+
describe 'manual clicks', ->
|
15
|
+
|
16
|
+
it 'fires when clicking on the container directly', ->
|
17
|
+
button = new window.Button($('#test #button1'))
|
18
|
+
button.bind_event('clicked', (spy = jasmine.createSpy()))
|
19
|
+
|
20
|
+
button.container.click()
|
21
|
+
|
22
|
+
expect(spy).toHaveBeenCalled()
|
23
|
+
expect(spy.callCount).toEqual(1)
|
24
|
+
|
25
|
+
|
26
|
+
it 'fires when clicking embedded elements of the button', ->
|
27
|
+
button = new window.Button($('#test #button2'))
|
28
|
+
button.bind_event('clicked', (spy = jasmine.createSpy()))
|
29
|
+
|
30
|
+
button.container.find('.embedded').click()
|
31
|
+
|
32
|
+
expect(spy).toHaveBeenCalled()
|
33
|
+
expect(spy.callCount).toEqual(1)
|
34
|
+
|
35
|
+
|
36
|
+
describe 'programmatic clicks', ->
|
37
|
+
|
38
|
+
it 'programmatically clicks the button', ->
|
39
|
+
button = new window.Button($('#test #button2'))
|
40
|
+
spy = jasmine.createSpy()
|
41
|
+
button.bind_event('clicked', spy)
|
42
|
+
|
43
|
+
button.click()
|
44
|
+
|
45
|
+
expect(spy).toHaveBeenCalled()
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#= require spec_helper
|
2
|
+
|
3
|
+
describe 'test environment setup', ->
|
4
|
+
|
5
|
+
it 'loading libraries', ->
|
6
|
+
load_modularity()
|
7
|
+
loadCS "/vendor/assets/javascripts/mixins/clickable.coffee"
|
8
|
+
loadCS "/vendor/assets/javascripts/modules/button.coffee"
|
9
|
+
loadCS "/vendor/assets/javascripts/modules/counter_button.coffee"
|
10
|
+
|
11
|
+
|
12
|
+
describe 'CounterButton', ->
|
13
|
+
template 'button.html'
|
14
|
+
|
15
|
+
describe 'standard button functionality', ->
|
16
|
+
|
17
|
+
describe 'manual clicks', ->
|
18
|
+
|
19
|
+
it 'fires when clicking on the container directly', ->
|
20
|
+
button = new window.CounterButton('#test #button1')
|
21
|
+
button.bind_event('clicked', (spy = jasmine.createSpy()))
|
22
|
+
|
23
|
+
button.container.click()
|
24
|
+
|
25
|
+
expect(spy).toHaveBeenCalled()
|
26
|
+
expect(spy.callCount).toEqual(1)
|
27
|
+
|
28
|
+
|
29
|
+
it 'fires when clicking embedded elements of the button', ->
|
30
|
+
button = new window.CounterButton('#test #button2')
|
31
|
+
button.bind_event('clicked', (spy = jasmine.createSpy()))
|
32
|
+
|
33
|
+
button.container.find('.embedded').click()
|
34
|
+
|
35
|
+
expect(spy).toHaveBeenCalled()
|
36
|
+
expect(spy.callCount).toEqual(1)
|
37
|
+
|
38
|
+
describe 'programmatic clicks', ->
|
39
|
+
|
40
|
+
it 'programmatically clicks the button', ->
|
41
|
+
button = new window.CounterButton('#test #button2')
|
42
|
+
spy = jasmine.createSpy()
|
43
|
+
button.bind_event('clicked', spy)
|
44
|
+
|
45
|
+
button.click()
|
46
|
+
|
47
|
+
expect(spy).toHaveBeenCalled()
|
48
|
+
|
49
|
+
describe 'CounterButton specific functionality', ->
|
50
|
+
|
51
|
+
it 'provides the click counter as the event payload', ->
|
52
|
+
button = new window.CounterButton('#test #button1')
|
53
|
+
button.bind_event('clicked', (spy = jasmine.createSpy()))
|
54
|
+
|
55
|
+
button.container.click()
|
56
|
+
button.container.click()
|
57
|
+
button.container.click()
|
58
|
+
|
59
|
+
expect(spy.callCount).toEqual(3)
|
60
|
+
expect(spy.argsForCall[0][1]).toEqual(1)
|
61
|
+
expect(spy.argsForCall[1][1]).toEqual(2)
|
62
|
+
expect(spy.argsForCall[2][1]).toEqual(3)
|
63
|
+
|
64
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require('/spec/javascripts/external/jquery.min.js')
|
2
|
+
require('/spec/javascripts/external/jasmine-jquery.js')
|
3
|
+
require('/spec/javascripts/external/coffee-script.js')
|
4
|
+
|
5
|
+
# Helper method to circumvent that Evergreen doesn't load CoffeeScript files.
|
6
|
+
window.loadCS = (url, callback) ->
|
7
|
+
$.ajax
|
8
|
+
url: url
|
9
|
+
async: false
|
10
|
+
success: (data) ->
|
11
|
+
eval CoffeeScript.compile data
|
12
|
+
callback() if callback
|
13
|
+
|
14
|
+
|
15
|
+
window.load_modularity = ->
|
16
|
+
loadCS "/vendor/assets/javascripts/modularity.js.coffee?#{(new Date()).getTime()}"
|
17
|
+
|
18
|
+
|
19
|
+
beforeEach ->
|
20
|
+
@addMatchers
|
21
|
+
toHaveLength: (expected) ->
|
22
|
+
this.length == expected
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This mixin adds a 'clickable' aspect to modules.
|
2
|
+
# This means clicking anywhere on the module fires the 'clicked' event.
|
3
|
+
window.clickable =
|
4
|
+
|
5
|
+
constructor: ->
|
6
|
+
@container.click @container_clicked
|
7
|
+
|
8
|
+
|
9
|
+
# Events that are fired by this mixin.
|
10
|
+
events:
|
11
|
+
clicked: 'clicked'
|
12
|
+
|
13
|
+
|
14
|
+
# Programmatically click this clickable element.
|
15
|
+
# For testing and scripting.
|
16
|
+
click: -> @container.click()
|
17
|
+
|
18
|
+
|
19
|
+
# Event handler for clicks on this clickable element.
|
20
|
+
container_clicked: -> @fire_event clickable.events.clicked
|
21
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
window.closable =
|
2
|
+
|
3
|
+
constructor: (container, close_button = undefined) ->
|
4
|
+
console.log close_button
|
5
|
+
close_button ||= @container.find('.CloseButton')
|
6
|
+
# TODO: add alert if no close button found.
|
7
|
+
close_button.click @close_button_clicked
|
8
|
+
|
9
|
+
|
10
|
+
# The events that can be fired by closable objects.
|
11
|
+
events:
|
12
|
+
closed: 'closed'
|
13
|
+
|
14
|
+
|
15
|
+
# Called when the button got clicked by the user or programmatically.
|
16
|
+
close_button_clicked: ->
|
17
|
+
@fire_event 'closed'
|
18
|
+
@container.remove()
|
@@ -19,14 +19,15 @@ class window.Module
|
|
19
19
|
|
20
20
|
|
21
21
|
if @mixins?
|
22
|
-
for
|
22
|
+
for mixin_data in @mixins
|
23
23
|
|
24
24
|
# Attach all properties from mixin to the prototype.
|
25
|
-
for methodName, method of mixin
|
26
|
-
@[methodName]
|
25
|
+
for methodName, method of mixin_data.mixin
|
26
|
+
unless @[methodName]
|
27
|
+
@[methodName] = => method.call(@, mixin_data.params)
|
27
28
|
|
28
29
|
# Call constructor function from mixin.
|
29
|
-
mixin.constructor.apply @, arguments
|
30
|
+
mixin_data.mixin.constructor.apply @, arguments
|
30
31
|
|
31
32
|
|
32
33
|
# Checks whether the given condition is true.
|
@@ -53,9 +54,10 @@ class window.Module
|
|
53
54
|
|
54
55
|
# mixin = constructor of Draggable
|
55
56
|
# self = Card
|
56
|
-
@mixin: (mixin) ->
|
57
|
-
@prototype.mixins = []
|
58
|
-
|
57
|
+
@mixin: (mixin, p...) ->
|
58
|
+
@prototype.mixins or= []
|
59
|
+
console.log p
|
60
|
+
@prototype.mixins.push({mixin: mixin, params: p})
|
59
61
|
|
60
62
|
|
61
63
|
# GLOBAL EVENTS.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# A button that counts how often it is clicked.
|
2
|
+
# This is implemented as a subclass of Button,
|
3
|
+
# to take advantage of the already existing functionality there.
|
4
|
+
class window.CounterButton extends Button
|
5
|
+
|
6
|
+
constructor: ->
|
7
|
+
super
|
8
|
+
|
9
|
+
# Counts how often this button has been clicked so far.
|
10
|
+
@click_count = 0
|
11
|
+
|
12
|
+
|
13
|
+
# We override the event handler for the 'clicked' event here.
|
14
|
+
container_clicked: =>
|
15
|
+
@fire_event 'clicked', ++@click_count
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modularity-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &70322603498400 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.1.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70322603498400
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: capybara-webkit
|
27
|
-
requirement: &
|
27
|
+
requirement: &70322603497980 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70322603497980
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: evergreen
|
38
|
-
requirement: &
|
38
|
+
requirement: &70322603497520 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,29 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70322603497520
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rb-fsevent
|
49
|
+
requirement: &70322603497060 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70322603497060
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard-livereload
|
60
|
+
requirement: &70322603496640 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70322603496640
|
47
69
|
description: Description of ModularityRails.
|
48
70
|
email:
|
49
71
|
- kevin.goslar@gmail.com
|
@@ -55,6 +77,7 @@ files:
|
|
55
77
|
- .gitignore
|
56
78
|
- .travis.yml
|
57
79
|
- Gemfile
|
80
|
+
- Guardfile
|
58
81
|
- LICENSE
|
59
82
|
- README.md
|
60
83
|
- Rakefile
|
@@ -63,10 +86,21 @@ files:
|
|
63
86
|
- lib/modularity-rails/version.rb
|
64
87
|
- modularity-rails.gemspec
|
65
88
|
- spec/javascripts/external/coffee-script.js
|
89
|
+
- spec/javascripts/external/jasmine-jquery.js
|
66
90
|
- spec/javascripts/external/jquery.min.js
|
91
|
+
- spec/javascripts/mixins/closable_spec.coffee
|
67
92
|
- spec/javascripts/modularity_spec.coffee
|
93
|
+
- spec/javascripts/modules/button_spec.coffee
|
94
|
+
- spec/javascripts/modules/counter_button_spec.coffee
|
95
|
+
- spec/javascripts/spec_helper.coffee
|
96
|
+
- spec/javascripts/templates/button.html
|
97
|
+
- spec/javascripts/templates/closable.html
|
68
98
|
- spec/javascripts/templates/test.html
|
99
|
+
- vendor/assets/javascripts/mixins/clickable.coffee
|
100
|
+
- vendor/assets/javascripts/mixins/closable.coffee
|
69
101
|
- vendor/assets/javascripts/modularity.js.coffee
|
102
|
+
- vendor/assets/javascripts/modules/button.coffee
|
103
|
+
- vendor/assets/javascripts/modules/counter_button.coffee
|
70
104
|
homepage: http://github.com/kevgo/modularity-rails
|
71
105
|
licenses: []
|
72
106
|
post_install_message:
|