apotomo 1.0.0.beta2 → 1.0.0

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/Gemfile CHANGED
@@ -3,7 +3,7 @@ source "http://rubygems.org"
3
3
  gem "rails", "~> 3.0.0"
4
4
  gem "cells", "~> 3.4"
5
5
  gem "onfire"
6
- gem "hooks", "~> 0.1.2"
6
+ gem "hooks", "~> 0.1.3"
7
7
 
8
8
 
9
9
  gem "jeweler"
@@ -12,3 +12,4 @@ gem "jeweler"
12
12
  gem "shoulda"
13
13
  gem "mocha"
14
14
  gem 'sqlite3-ruby', '1.2.5', :require => 'sqlite3' # needed in router_test, whatever.
15
+ gem "capybara"
data/README.rdoc CHANGED
@@ -22,7 +22,7 @@ Easy as hell.
22
22
 
23
23
  === Rails 3
24
24
 
25
- gem install apotomo
25
+ gem install apotomo --pre
26
26
 
27
27
  === Rails 2.3
28
28
 
@@ -42,12 +42,12 @@ Let's wrap that comments block in a widget.
42
42
 
43
43
  Go and generate a widget stub.
44
44
 
45
- $ rails generate apotomo:widget CommentsWidget display process --haml
45
+ $ rails generate apotomo:widget CommentsWidget display write --haml
46
46
  create app/cells/
47
47
  create app/cells/comments_widget
48
48
  create app/cells/comments_widget.rb
49
49
  create app/cells/comments_widget/display.html.haml
50
- create app/cells/comments_widget/process.html.haml
50
+ create app/cells/comments_widget/write.html.haml
51
51
  create test/widgets/comments_widget_test.rb
52
52
 
53
53
  Nothing special.
@@ -60,10 +60,10 @@ You now tell your controller about the new widget.
60
60
  include Apotomo::Rails::ControllerMethods
61
61
 
62
62
  has_widgets do |root|
63
- root << widget('comments_widget', 'post-comments', :display, :post => post)
63
+ root << widget('comments_widget', 'post-comments', :post => @post)
64
64
  end
65
65
 
66
- The widget is named <tt>post-comments</tt> and jumps to the <tt>:display</tt> state per default. Assuming your controller has a method +post+ we pass the current post into the widget.
66
+ The widget is named <tt>post-comments</tt>. We pass the current post into the widget - the block is executed in controller instance context, that's were <tt>@post</tt> comes from. Handy, isn't it?
67
67
 
68
68
  == Render the widget
69
69
 
@@ -82,18 +82,18 @@ Rendering usually happens in your controller view, <tt>views/posts/show.haml</tt
82
82
  A widget is like a cell which is like a mini-controller.
83
83
 
84
84
  class CommentsWidget < Apotomo::Widget
85
- responds_to_event :submit, :with => :process
85
+ responds_to_event :submit, :with => :write
86
86
 
87
87
  def display
88
88
  @comments = param(:post).comments # the parameter from outside.
89
89
  render
90
90
  end
91
91
 
92
- The +display+ state collects comments to show and renders its view.
92
+ Having +display+ as the default state when rendering, this method collects comments to show and renders its view.
93
93
 
94
- And look at line 2 - if encountering a <tt>:submit</tt> event we invoke +process+, which is simply another state. How cool is that?
94
+ And look at line 2 - if encountering a <tt>:submit</tt> event we invoke +write+, which is simply another state. How cool is that?
95
95
 
96
- def process
96
+ def write
97
97
  @comment = Comment.new(:post => param(:post))
98
98
  @comment.update_attributes param(:comment) # like params[].
99
99
 
@@ -160,7 +160,7 @@ Look, +replace+ basically generates
160
160
 
161
161
  If that's not what you want, do
162
162
 
163
- def process
163
+ def write
164
164
  if param(:comment)[:text].explicit?
165
165
  render :text => 'alert("Hey, you wanted to submit a pervert comment!");'
166
166
  end
@@ -168,6 +168,25 @@ If that's not what you want, do
168
168
 
169
169
  Apotomo doesn't depend on _any_ JS framework - you choose!
170
170
 
171
+ == Testing
172
+
173
+ Apotomo comes with its own test case and assertions to <b>build rock-solid web components</b>.
174
+
175
+ class CommentsWidgetTest < Apotomo::TestCase
176
+ has_widgets do |root|
177
+ root << widget(:comments_widget, 'me', :post => @pervert_post)
178
+ end
179
+
180
+ def test_render
181
+ render_widget 'me'
182
+ assert_select "li#me"
183
+
184
+ trigger :submit, :comment => {:text => "Sex on the beach"}
185
+ assert_response 'alert("Hey, you wanted to submit a pervert comment!");'
186
+ end
187
+ end
188
+
189
+ You can render your widgets, spec the markup, trigger events and assert the event responses, so far. If you need more, let us know!
171
190
 
172
191
  == More features
173
192
 
@@ -175,8 +194,7 @@ There's even more, too much for a simple README.
175
194
 
176
195
  [Statefulness] Deriving your widget from +StatefulWidget+ gives you free statefulness.
177
196
  [Composability] Widgets can range from small standalone components to nested widget trees like complex dashboards.
178
- [Bubbling events] Events bubble up from their triggering source to root and thus can be observed, providing a way to implement loosely coupled, distributable components.
179
- [Testing] Apotomo comes with testing assertions to build rock-solid web components.
197
+ [Bubbling events] Events bubble up from their triggering source to root and thus can be observed, providing a way to implement loosely coupled, distributable components.
180
198
  [Team-friendly] Widgets encourage encapsulation and help having different developers working on different components without getting out of bounds.
181
199
 
182
200
 
data/Rakefile CHANGED
@@ -25,7 +25,7 @@ Jeweler::Tasks.new do |spec|
25
25
  spec.name = "apotomo"
26
26
  spec.version = ::Apotomo::VERSION
27
27
  spec.summary = %{Web components for Rails.}
28
- spec.description = "Web components for Rails. Event-driven. Clean. Fast. Free optional statefulness included."
28
+ spec.description = "Web component framework for Rails providing page widgets that trigger events and know when and how to update themselves with AJAX."
29
29
  spec.homepage = "http://apotomo.de"
30
30
  spec.authors = ["Nick Sutterer"]
31
31
  spec.email = "apotonick@gmail.com"
@@ -36,7 +36,7 @@ Jeweler::Tasks.new do |spec|
36
36
  spec.add_dependency 'cells', '~> 3.4.2'
37
37
  spec.add_dependency 'rails', '>= 3.0.0'
38
38
  spec.add_dependency 'onfire', '>= 0.1.0'
39
- spec.add_dependency 'hooks', '~> 0.1.2'
39
+ spec.add_dependency 'hooks', '~> 0.1.3'
40
40
  end
41
41
 
42
42
  Jeweler::GemcutterTasks.new
data/config/routes.rb CHANGED
@@ -1,3 +1,3 @@
1
- Rails.application.routes.draw do |map|
1
+ Rails.application.routes.draw do
2
2
  match ":controller/render_event_response", :to => "#render_event_response", :as => "apotomo_event"
3
3
  end
@@ -53,8 +53,7 @@ module Apotomo
53
53
  # url_for_event(:paginate, :page => 2)
54
54
  # #=> http://apotomo.de/mouse/process_event_request?type=paginate&source=mouse&page=2
55
55
  def url_for_event(type, options={})
56
- options.reverse_merge! :source => widget_id
57
- @cell.url_for_event(type, options) # FIXME: don't access @parent_controller but @cell.
56
+ @cell.url_for_event(type, options)
58
57
  end
59
58
 
60
59
  ### TODO: test me.
@@ -17,8 +17,6 @@ module Apotomo
17
17
 
18
18
  flushed_root ### FIXME: set internal mode to flushed
19
19
  end
20
-
21
- #handle_version!(options[:version])
22
20
  end
23
21
 
24
22
  def attach_stateless_blocks_for(blocks, root, controller)
@@ -30,16 +28,7 @@ module Apotomo
30
28
  @widgets_flushed = true
31
29
  #widget('apotomo/widget', 'root')
32
30
  end
33
-
34
- ### DISCUSS: do we need the version feature, or should we push that into user code?
35
- def handle_version!(version)
36
- return if version.blank?
37
- return if root.version == version
38
-
39
- @root = flushed_root
40
- @root.version = version
41
- end
42
-
31
+
43
32
  def widgets_flushed?; @widgets_flushed; end
44
33
 
45
34
  # Fires the request event in the widget tree and collects the rendered page updates.
@@ -4,5 +4,26 @@ require 'apotomo/persistence'
4
4
  module Apotomo
5
5
  class StatefulWidget < Widget
6
6
  include Persistence
7
+
8
+ attr_accessor :version
9
+
10
+ def initialize(*)
11
+ super
12
+ @version = 0
13
+ end
14
+
15
+
16
+ # Defines the instance vars that should <em>not</em> survive between requests,
17
+ # which means they're not frozen in Apotomo::StatefulWidget#freeze.
18
+ def ivars_to_forget
19
+ unfreezable_ivars
20
+ end
21
+
22
+ def unfreezable_ivars
23
+ [:@childrenHash, :@children, :@parent, :@parent_controller, :@_request, :@_config, :@cell, :@invoke_block, :@rendered_children, :@page_updates, :@opts, :@params,
24
+ :@suppress_javascript ### FIXME: implement with ActiveHelper and :locals.
25
+
26
+ ]
27
+ end
7
28
  end
8
- end
29
+ end
@@ -0,0 +1,105 @@
1
+ require 'cell/test_case'
2
+
3
+ module Apotomo
4
+ # Testing is fun. Test your widgets!
5
+ #
6
+ # This class helps you testing widgets where it can. It is similar as in a controller.
7
+ # A declarative test would look like
8
+ #
9
+ # class BlogWidgetTest < Apotomo::TestCase
10
+ # has_widgets do |root|
11
+ # root << widget(:comments_widget, 'post-comments')
12
+ # end
13
+ #
14
+ # it "should be rendered nicely" do
15
+ # render_widget 'post-comments'
16
+ #
17
+ # assert_select "div#post-comments", "Comments for this post"
18
+ # end
19
+ #
20
+ # it "should redraw on :update" do
21
+ # trigger :update
22
+ # assert_response "$(\"post-comments\").update ..."
23
+ # end
24
+ #
25
+ # For unit testing, you can grab an instance of your tested widget.
26
+ #
27
+ # it "should be visible" do
28
+ # assert root['post-comments'].visible?
29
+ # end
30
+ #
31
+ # See also in Cell::TestCase.
32
+ class TestCase < Cell::TestCase
33
+ class << self
34
+ def has_widgets_blocks; @has_widgets; end
35
+
36
+ # Setup a widget tree as you're used to it from your controller. Executed in test context.
37
+ def has_widgets(&block)
38
+ @has_widgets = block # DISCUSS: use ControllerMethods?
39
+ end
40
+ end
41
+
42
+ def setup
43
+ super
44
+ @controller.instance_eval do
45
+ def controller_name
46
+ 'barn'
47
+ end
48
+ end
49
+ @controller.extend Apotomo::Rails::ControllerMethods
50
+ end
51
+
52
+
53
+ # Returns the widget tree from TestCase.has_widgets.
54
+ def root
55
+ blk = self.class.has_widgets_blocks or raise "Please setup a widget tree using TestCase.has_widgets"
56
+ @root ||= widget("apotomo/widget", "root").tap do |root|
57
+ self.instance_exec(root, &blk)
58
+ end
59
+ end
60
+
61
+ def parent_controller
62
+ @controller
63
+ end
64
+
65
+ # Renders the widget +name+.
66
+ def render_widget(name, options={})
67
+ @last_invoke = root.find_widget(name).tap { |w| w.opts = options }.invoke # DISCUSS: use ControllerMethods?
68
+ end
69
+
70
+ # Triggers an event of +type+. You have to pass <tt>:source</tt> as options.
71
+ #
72
+ # Example:
73
+ #
74
+ # trigger :submit, :source => "post-comments"
75
+ def trigger(type, options)
76
+ source = root.find_widget(options.delete(:source))
77
+ source.instance_variable_set :@params, options # TODO: this is just a try-out (what about children?).
78
+ source.fire(type)
79
+ root.page_updates # DISCUSS: use ControllerMethods?
80
+ end
81
+
82
+ # After a #trigger this assertion compares the actually triggered page updates with the passed.
83
+ #
84
+ # Example:
85
+ #
86
+ # trigger :submit, :source => "post-comments"
87
+ # assert_response "alert(\":submit clicked!\")", /\$\("post-comments"\).update/
88
+ def assert_response(*content)
89
+ updates = root.page_updates
90
+
91
+ i = 0
92
+ content.each do |assertion|
93
+ if assertion.kind_of? Regexp
94
+ assert_match assertion, updates[i]
95
+ else
96
+ assert_equal assertion, updates[i]
97
+ end
98
+
99
+ i+=1
100
+ end
101
+ end
102
+
103
+ include Apotomo::WidgetShortcuts
104
+ end
105
+ end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Apotomo
4
- VERSION = '1.0.0.beta2'
2
+ VERSION = '1.0.0'
5
3
  end
@@ -6,7 +6,6 @@ require 'apotomo/tree_node'
6
6
  require 'apotomo/event'
7
7
  require 'apotomo/event_methods'
8
8
  require 'apotomo/transition'
9
- require 'apotomo/caching'
10
9
  require 'apotomo/widget_shortcuts'
11
10
  require 'apotomo/rails/view_helper'
12
11
 
@@ -31,9 +30,8 @@ module Apotomo
31
30
 
32
31
  attr_accessor :opts
33
32
  attr_writer :visible
34
-
33
+
35
34
  attr_writer :controller
36
- attr_accessor :version
37
35
 
38
36
  include TreeNode
39
37
 
@@ -41,7 +39,6 @@ module Apotomo
41
39
  include EventMethods
42
40
 
43
41
  include Transition
44
- include Caching
45
42
  include WidgetShortcuts
46
43
 
47
44
  helper Apotomo::Rails::ViewHelper
@@ -66,13 +63,11 @@ module Apotomo
66
63
  @start_state = start_state
67
64
 
68
65
  @visible = true
69
- @version = 0 ### DISCUSS: neeed in stateLESS?
70
-
71
66
  @cell = self ### DISCUSS: needed?
72
67
 
73
68
  @params = parent_controller.params.dup.merge(opts)
74
69
 
75
- run_hook(:after_initialize, id, start_state, opts)
70
+ run_hook :after_initialize, self
76
71
  end
77
72
 
78
73
  def last_state
@@ -82,29 +77,6 @@ module Apotomo
82
77
  def visible?
83
78
  @visible
84
79
  end
85
-
86
- # Defines the instance vars that should <em>not</em> survive between requests,
87
- # which means they're not frozen in Apotomo::StatefulWidget#freeze.
88
- def ivars_to_forget
89
- unfreezable_ivars
90
- end
91
-
92
- def unfreezable_ivars
93
- [:@childrenHash, :@children, :@parent, :@parent_controller, :@_request, :@_config, :@cell, :@invoke_block, :@rendered_children, :@page_updates, :@opts, :@params,
94
- :@suppress_javascript ### FIXME: implement with ActiveHelper and :locals.
95
-
96
- ]
97
- end
98
-
99
- # Defines the instance vars which should <em>not</em> be copied to the view.
100
- # Called in Cell::Base.
101
- def ivars_to_ignore
102
- []
103
- end
104
-
105
- ### FIXME:
106
- def logger; self; end
107
- def debug(*args); puts args; end
108
80
 
109
81
  # Returns the rendered content for the widget by running the state method for <tt>state</tt>.
110
82
  # This might lead us to some other state since the state method could call #jump_to_state.
@@ -256,36 +228,19 @@ module Apotomo
256
228
  end
257
229
 
258
230
 
259
- # Returns the address hash to the event controller and the targeted widget.
260
- #
261
- # Reserved options for <tt>way</tt>:
262
- # :source explicitly specifies an event source.
263
- # The default is to take the current widget as source.
264
- # :type specifies the event type.
265
- #
266
- # Any other option will be directly passed into the address hash and is
267
- # available via StatefulWidget#param in the widget.
268
- #
269
- # Can be passed to #url_for.
270
- #
271
- # Example:
272
- # address_for_event :type => :squeak, :volume => 9
273
- # will result in an address that triggers a <tt>:click</tt> event from the current
274
- # widget and also provides the parameter <tt>:item_id</tt>.
275
- def address_for_event(options)
276
- raise "please specify the event :type" unless options[:type]
277
-
278
- options[:source] ||= self.name
279
- options
280
- end
281
-
282
231
  # Returns the widget named <tt>widget_id</tt> as long as it is below self or self itself.
283
232
  def find_widget(widget_id)
284
233
  find {|node| node.name.to_s == widget_id.to_s}
285
234
  end
286
235
 
287
- def url_for_event(*args)
288
- parent_controller.url_for_event(*args)
236
+ def address_for_event(type, options={})
237
+ options.reverse_merge! :source => name,
238
+ :type => type,
239
+ :controller => parent_controller.controller_name # DISCUSS: dependency to parent_controller.
240
+ end
241
+
242
+ def url_for_event(type, options={})
243
+ apotomo_event_path address_for_event(type, options)
289
244
  end
290
245
 
291
246
  alias_method :widget_id, :name
data/lib/apotomo.rb CHANGED
@@ -20,6 +20,9 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
+ require "rails/railtie"
24
+ require 'rails/engine'
25
+
23
26
  module Apotomo
24
27
  class << self
25
28
  def js_framework=(js_framework)
@@ -43,13 +46,24 @@ module Apotomo
43
46
 
44
47
  class Engine < Rails::Engine
45
48
  end
49
+
50
+ class Railtie < Rails::Railtie
51
+ rake_tasks do
52
+ load "tasks.rake"
53
+ end
54
+ end
46
55
  end
47
56
 
48
- require 'apotomo/javascript_generator'
49
- Apotomo.js_framework = :jquery ### DISCUSS: move to rails.rb
50
57
 
51
58
  require 'apotomo/widget'
52
59
  require 'apotomo/stateful_widget'
53
60
  require 'apotomo/container_widget'
54
61
  require 'apotomo/widget_shortcuts'
55
62
  require 'apotomo/rails/controller_methods'
63
+
64
+
65
+ require 'apotomo/javascript_generator'
66
+ Apotomo.js_framework = :jquery ### DISCUSS: move to rails.rb
67
+
68
+ ### FIXME: only load in test env.
69
+ require 'apotomo/test_case' #if defined?("Rails") and Rails.env == "test"
@@ -1,11 +1,12 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class <%= class_name %>Test < Apotomo::TestCase
4
- <% for state in @states -%>
5
- test "<%= state %>" do
6
- invoke :<%= state %>
7
- assert_select "p"
4
+ has_widgets do |root|
5
+ root << widget(:<%= file_name %>, 'me')
8
6
  end
9
7
 
10
- <% end %>
8
+ test "display" do
9
+ render_widget 'me'
10
+ assert_select "h1"
11
+ end
11
12
  end
data/lib/tasks.rake ADDED
@@ -0,0 +1,6 @@
1
+ namespace "test" do
2
+ TestTaskWithoutDescription.new(:widgets => "test:prepare") do |t|
3
+ t.libs << "test"
4
+ t.pattern = 'test/widgets/**/*_test.rb'
5
+ end
6
+ end
@@ -20,6 +20,7 @@ class WidgetGeneratorTest < Rails::Generators::TestCase
20
20
  assert_file "app/cells/mouse_widget/squeak.html.erb", %r(app/cells/mouse_widget/squeak\.html\.erb)
21
21
 
22
22
  assert_file "test/widgets/mouse_widget_test.rb", %r(class MouseWidgetTest < Apotomo::TestCase)
23
+ assert_file "test/widgets/mouse_widget_test.rb", %r(widget\(:mouse_widget, 'me'\))
23
24
  end
24
25
 
25
26
  should "create haml assets with --haml" do
data/test/test_helper.rb CHANGED
@@ -3,24 +3,16 @@ require 'rubygems'
3
3
  require 'bundler'
4
4
  Bundler.setup
5
5
 
6
- #require 'rubygems'
7
6
  require 'shoulda'
8
- require 'mocha'
9
- require 'mocha/integration'
10
-
11
-
12
7
  require 'cells'
13
- Cell::Base.append_view_path File.expand_path(File.dirname(__FILE__) + "/fixtures")
14
-
15
- require 'rails/engine'
16
-
17
8
  require 'apotomo'
18
- require 'apotomo/widget_shortcuts'
19
- require 'apotomo/rails/controller_methods'
20
- require 'apotomo/rails/view_methods'
21
9
 
10
+ ENV['RAILS_ENV'] = 'test'
11
+ require "dummy/config/environment"
12
+ require "rails/test_help" # sets up ActionController::TestCase's @routes
22
13
 
23
14
 
15
+ Cell::Base.append_view_path File.expand_path(File.dirname(__FILE__) + "/fixtures")
24
16
 
25
17
  # Load test support files.
26
18
  require File.join(File.dirname(__FILE__), "support/test_case_methods")
@@ -59,8 +51,3 @@ end
59
51
  class Apotomo::Widget
60
52
  def action_method?(*); true; end
61
53
  end
62
-
63
- ENV['RAILS_ENV'] = 'test'
64
- require "dummy/config/environment"
65
- #require File.join(File.dirname(__FILE__), '..', 'config/routes.rb') ### TODO: let rails engine handle that.
66
- require "rails/test_help" # sets up ActionController::TestCase's @routes
@@ -59,10 +59,6 @@ class RequestProcessorTest < ActiveSupport::TestCase
59
59
  should "provide a single root-node for #root" do
60
60
  assert_equal 1, @processor.root.size
61
61
  end
62
-
63
- should "initialize version to 0" do
64
- assert_equal 0, @processor.root.version
65
- end
66
62
  end
67
63
 
68
64
  context "with controller" do
@@ -0,0 +1,95 @@
1
+ require 'test_helper'
2
+ require 'apotomo/test_case'
3
+
4
+ class TestCaseTest < Test::Unit::TestCase
5
+
6
+ class CommentsWidgetTest < Apotomo::TestCase
7
+ end
8
+
9
+ class CommentsWidget < Apotomo::Widget
10
+ end
11
+
12
+ context "TestCase" do
13
+
14
+ context "responding to #root" do
15
+ class MouseWidgetTest < Apotomo::TestCase
16
+ end
17
+
18
+ setup do
19
+ @klass = MouseWidgetTest
20
+ @test = @klass.new(:widget).tap{ |t| t.setup }
21
+ @klass.has_widgets { |r| r << widget("mouse_cell", 'mum', :eating) }
22
+ end
23
+
24
+ should "respond to #root" do
25
+ assert_equal ['root', 'mum'], @test.root.collect { |w| w.name }
26
+ end
27
+
28
+ should "raise an error if no has_widgets block given" do
29
+ exc = assert_raises RuntimeError do
30
+ @test = Class.new(Apotomo::TestCase).new(:widget).tap{ |t| t.setup }
31
+ @test.root
32
+ end
33
+
34
+ assert_equal "Please setup a widget tree using TestCase.has_widgets", exc.message
35
+ end
36
+
37
+ should "memorize root" do
38
+ @test.root.visible=false
39
+ assert_equal false, @test.root.visible?
40
+ end
41
+
42
+ should "respond to #render_widget" do
43
+ assert_equal "<div id=\"mum\">burp!</div>", @test.render_widget('mum')
44
+ assert_equal "<div id=\"mum\">burp!</div>", @test.last_invoke
45
+ end
46
+
47
+ should "respond to #assert_select" do
48
+ @test.render_widget('mum')
49
+
50
+ assert_nothing_raised { @test.assert_select("div#mum", "burp!") }
51
+
52
+ exc = assert_raises( MiniTest::Assertion){ @test.assert_select("div#mummy", "burp!"); }
53
+ assert_equal 'Expected at least 1 element matching "div#mummy", found 0.', exc.message
54
+ end
55
+
56
+ context "using events" do
57
+ setup do
58
+ @mum = @test.root['mum']
59
+ @mum.respond_to_event :footsteps, :with => :squeak
60
+ @mum.instance_eval do
61
+ def squeak; render :text => "squeak!"; end
62
+ end
63
+ end
64
+
65
+ should "respond to #trigger" do
66
+ assert_equal ["squeak!"], @test.trigger(:footsteps, :source => 'mum')
67
+ end
68
+
69
+ should "provide options from #trigger to the widget" do
70
+ @test.trigger(:footsteps, :source => 'mum', :direction => :kitchen)
71
+ assert_equal :kitchen, @mum.param(:direction)
72
+ end
73
+
74
+ should "respond to #assert_response" do
75
+ @test.trigger(:footsteps, :source => 'mum')
76
+ assert @test.assert_response("squeak!")
77
+ end
78
+ end
79
+ end
80
+
81
+ context "responding to parent_controller" do
82
+ setup do
83
+ @test = Apotomo::TestCase.new(:widget).tap{ |t| t.setup }
84
+ end
85
+
86
+ should "provide a test controller" do
87
+ assert_kind_of ActionController::Base, @test.parent_controller
88
+ end
89
+
90
+ should "respond to #controller_name" do
91
+ assert_equal "barn", @test.parent_controller.controller_name
92
+ end
93
+ end
94
+ end
95
+ end
@@ -65,21 +65,15 @@ class WidgetTest < ActiveSupport::TestCase
65
65
 
66
66
  context "responding to #address_for_event" do
67
67
  should "accept an event :type" do
68
- assert_equal({:type => :squeak, :source => 'mum'}, @mum.address_for_event(:type => :squeak))
68
+ assert_equal({:source=>"mum", :type=>:squeak, :controller=>"barn"}, @mum.address_for_event(:squeak))
69
69
  end
70
70
 
71
71
  should "accept a :source" do
72
- assert_equal({:type => :squeak, :source => 'kid'}, @mum.address_for_event(:type => :squeak, :source => 'kid'))
72
+ assert_equal({:source=>"kid", :type=>:squeak, :controller=>"barn"}, @mum.address_for_event(:squeak, :source => 'kid'))
73
73
  end
74
74
 
75
75
  should "accept arbitrary options" do
76
- assert_equal({:type => :squeak, :volume => 'loud', :source => 'mum'}, @mum.address_for_event(:type => :squeak, :volume => 'loud'))
77
- end
78
-
79
- should "complain if no type given" do
80
- assert_raises RuntimeError do
81
- @mum.address_for_event(:source => 'mum')
82
- end
76
+ assert_equal({:volume=>"loud", :source=>"mum", :type=>:squeak, :controller=>"barn"}, @mum.address_for_event(:squeak, :volume => 'loud'))
83
77
  end
84
78
  end
85
79
 
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apotomo
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ prerelease: false
5
5
  segments:
6
6
  - 1
7
7
  - 0
8
8
  - 0
9
- - beta2
10
- version: 1.0.0.beta2
9
+ version: 1.0.0
11
10
  platform: ruby
12
11
  authors:
13
12
  - Nick Sutterer
@@ -15,7 +14,7 @@ autorequire:
15
14
  bindir: bin
16
15
  cert_chain: []
17
16
 
18
- date: 2010-10-16 00:00:00 +02:00
17
+ date: 2010-11-17 00:00:00 +01:00
19
18
  default_executable:
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
@@ -74,11 +73,11 @@ dependencies:
74
73
  segments:
75
74
  - 0
76
75
  - 1
77
- - 2
78
- version: 0.1.2
76
+ - 3
77
+ version: 0.1.3
79
78
  type: :runtime
80
79
  version_requirements: *id004
81
- description: Web components for Rails. Event-driven. Clean. Fast. Free optional statefulness included.
80
+ description: Web component framework for Rails providing page widgets that trigger events and know when and how to update themselves with AJAX.
82
81
  email: apotonick@gmail.com
83
82
  executables: []
84
83
 
@@ -94,9 +93,7 @@ files:
94
93
  - TODO
95
94
  - config/routes.rb
96
95
  - lib/apotomo.rb
97
- - lib/apotomo/caching.rb
98
96
  - lib/apotomo/container_widget.rb
99
- - lib/apotomo/deep_link_methods.rb
100
97
  - lib/apotomo/event.rb
101
98
  - lib/apotomo/event_handler.rb
102
99
  - lib/apotomo/event_methods.rb
@@ -109,6 +106,7 @@ files:
109
106
  - lib/apotomo/rails/view_methods.rb
110
107
  - lib/apotomo/request_processor.rb
111
108
  - lib/apotomo/stateful_widget.rb
109
+ - lib/apotomo/test_case.rb
112
110
  - lib/apotomo/transition.rb
113
111
  - lib/apotomo/tree_node.rb
114
112
  - lib/apotomo/version.rb
@@ -120,72 +118,72 @@ files:
120
118
  - lib/generators/apotomo/templates/widget.rb
121
119
  - lib/generators/apotomo/templates/widget_test.rb
122
120
  - lib/generators/apotomo/widget_generator.rb
123
- - test/fixtures/mouse/feed.html.erb
124
- - test/fixtures/mouse/eating.html.erb
121
+ - lib/tasks.rake
125
122
  - test/fixtures/mouse/make_me_squeak.html.erb
126
- - test/fixtures/mouse/posing.html.erb
123
+ - test/fixtures/mouse/content.html.erb
127
124
  - test/fixtures/mouse/educate.html.erb
125
+ - test/fixtures/mouse/feed.html.erb
128
126
  - test/fixtures/mouse/snuggle.html.erb
129
- - test/fixtures/mouse/content.html.erb
127
+ - test/fixtures/mouse/eating.html.erb
128
+ - test/fixtures/mouse/posing.html.erb
130
129
  - test/fixtures/application_widget_tree.rb
131
- - test/rails/view_methods_test.rb
132
- - test/rails/controller_methods_test.rb
133
- - test/rails/view_helper_test.rb
134
- - test/rails/widget_generator_test.rb
135
- - test/rails/rails_integration_test.rb
136
130
  - test/test_helper.rb
137
- - test/support/test_case_methods.rb
138
- - test/dummy/config/application.rb
139
- - test/dummy/config/initializers/session_store.rb
140
- - test/dummy/config/initializers/mime_types.rb
141
- - test/dummy/config/initializers/secret_token.rb
142
- - test/dummy/config/initializers/inflections.rb
143
- - test/dummy/config/initializers/backtrace_silencers.rb
144
- - test/dummy/config/locales/en.yml
145
- - test/dummy/config/routes.rb
146
- - test/dummy/config/boot.rb
147
- - test/dummy/config/environment.rb
148
- - test/dummy/config/environments/production.rb
149
- - test/dummy/config/environments/test.rb
150
- - test/dummy/config/environments/development.rb
151
- - test/dummy/config/database.yml
152
- - test/dummy/script/rails
153
- - test/dummy/config.ru
154
- - test/dummy/db/test.sqlite3
155
131
  - test/dummy/Rakefile
156
- - test/dummy/public/422.html
157
- - test/dummy/public/favicon.ico
158
- - test/dummy/public/500.html
132
+ - test/dummy/app/controllers/application_controller.rb
133
+ - test/dummy/app/helpers/application_helper.rb
134
+ - test/dummy/app/views/layouts/application.html.erb
159
135
  - test/dummy/public/404.html
136
+ - test/dummy/public/javascripts/prototype.js
160
137
  - test/dummy/public/javascripts/controls.js
138
+ - test/dummy/public/javascripts/effects.js
139
+ - test/dummy/public/javascripts/dragdrop.js
161
140
  - test/dummy/public/javascripts/application.js
162
141
  - test/dummy/public/javascripts/rails.js
163
- - test/dummy/public/javascripts/dragdrop.js
164
- - test/dummy/public/javascripts/prototype.js
165
- - test/dummy/public/javascripts/effects.js
166
- - test/dummy/app/controllers/application_controller.rb
167
- - test/dummy/app/views/layouts/application.html.erb
168
- - test/dummy/app/helpers/application_helper.rb
169
- - test/unit/test_widget_shortcuts.rb
170
- - test/unit/event_handler_test.rb
171
- - test/unit/widget_shortcuts_test.rb
172
- - test/unit/stateful_widget_test.rb
173
- - test/unit/test_addressing.rb
142
+ - test/dummy/public/422.html
143
+ - test/dummy/public/favicon.ico
144
+ - test/dummy/public/500.html
145
+ - test/dummy/config.ru
146
+ - test/dummy/config/environments/test.rb
147
+ - test/dummy/config/environments/production.rb
148
+ - test/dummy/config/environments/development.rb
149
+ - test/dummy/config/database.yml
150
+ - test/dummy/config/boot.rb
151
+ - test/dummy/config/locales/en.yml
152
+ - test/dummy/config/environment.rb
153
+ - test/dummy/config/initializers/secret_token.rb
154
+ - test/dummy/config/initializers/backtrace_silencers.rb
155
+ - test/dummy/config/initializers/session_store.rb
156
+ - test/dummy/config/initializers/mime_types.rb
157
+ - test/dummy/config/initializers/inflections.rb
158
+ - test/dummy/config/application.rb
159
+ - test/dummy/config/routes.rb
160
+ - test/dummy/db/test.sqlite3
161
+ - test/dummy/script/rails
162
+ - test/support/test_case_methods.rb
163
+ - test/rails/rails_integration_test.rb
164
+ - test/rails/view_methods_test.rb
165
+ - test/rails/controller_methods_test.rb
166
+ - test/rails/view_helper_test.rb
167
+ - test/rails/widget_generator_test.rb
168
+ - test/unit/onfire_integration_test.rb
174
169
  - test/unit/invoke_test.rb
175
- - test/unit/container_test.rb
176
- - test/unit/test_tab_panel.rb
177
- - test/unit/test_jump_to_state.rb
170
+ - test/unit/request_processor_test.rb
178
171
  - test/unit/render_test.rb
172
+ - test/unit/test_case_test.rb
179
173
  - test/unit/apotomo_test.rb
180
- - test/unit/request_processor_test.rb
181
- - test/unit/test_caching.rb
182
174
  - test/unit/javascript_generator_test.rb
183
- - test/unit/onfire_integration_test.rb
175
+ - test/unit/stateful_widget_test.rb
176
+ - test/unit/event_methods_test.rb
184
177
  - test/unit/persistence_test.rb
178
+ - test/unit/event_handler_test.rb
185
179
  - test/unit/transition_test.rb
186
180
  - test/unit/event_test.rb
187
- - test/unit/event_methods_test.rb
188
181
  - test/unit/widget_test.rb
182
+ - test/unit/widget_shortcuts_test.rb
183
+ - test/unit/test_tab_panel.rb
184
+ - test/unit/test_addressing.rb
185
+ - test/unit/container_test.rb
186
+ - test/unit/test_jump_to_state.rb
189
187
  has_rdoc: true
190
188
  homepage: http://apotomo.de
191
189
  licenses: []
@@ -206,13 +204,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
204
  required_rubygems_version: !ruby/object:Gem::Requirement
207
205
  none: false
208
206
  requirements:
209
- - - ">"
207
+ - - ">="
210
208
  - !ruby/object:Gem::Version
211
209
  segments:
212
- - 1
213
- - 3
214
- - 1
215
- version: 1.3.1
210
+ - 0
211
+ version: "0"
216
212
  requirements: []
217
213
 
218
214
  rubyforge_project:
@@ -221,69 +217,68 @@ signing_key:
221
217
  specification_version: 3
222
218
  summary: Web components for Rails.
223
219
  test_files:
224
- - test/fixtures/mouse/feed.html.erb
225
- - test/fixtures/mouse/eating.html.erb
226
220
  - test/fixtures/mouse/make_me_squeak.html.erb
227
- - test/fixtures/mouse/posing.html.erb
221
+ - test/fixtures/mouse/content.html.erb
228
222
  - test/fixtures/mouse/educate.html.erb
223
+ - test/fixtures/mouse/feed.html.erb
229
224
  - test/fixtures/mouse/snuggle.html.erb
230
- - test/fixtures/mouse/content.html.erb
225
+ - test/fixtures/mouse/eating.html.erb
226
+ - test/fixtures/mouse/posing.html.erb
231
227
  - test/fixtures/application_widget_tree.rb
232
- - test/rails/view_methods_test.rb
233
- - test/rails/controller_methods_test.rb
234
- - test/rails/view_helper_test.rb
235
- - test/rails/widget_generator_test.rb
236
- - test/rails/rails_integration_test.rb
237
228
  - test/test_helper.rb
238
- - test/support/test_case_methods.rb
239
- - test/dummy/config/application.rb
240
- - test/dummy/config/initializers/session_store.rb
241
- - test/dummy/config/initializers/mime_types.rb
242
- - test/dummy/config/initializers/secret_token.rb
243
- - test/dummy/config/initializers/inflections.rb
244
- - test/dummy/config/initializers/backtrace_silencers.rb
245
- - test/dummy/config/locales/en.yml
246
- - test/dummy/config/routes.rb
247
- - test/dummy/config/boot.rb
248
- - test/dummy/config/environment.rb
249
- - test/dummy/config/environments/production.rb
250
- - test/dummy/config/environments/test.rb
251
- - test/dummy/config/environments/development.rb
252
- - test/dummy/config/database.yml
253
- - test/dummy/script/rails
254
- - test/dummy/config.ru
255
- - test/dummy/db/test.sqlite3
256
229
  - test/dummy/Rakefile
257
- - test/dummy/public/422.html
258
- - test/dummy/public/favicon.ico
259
- - test/dummy/public/500.html
230
+ - test/dummy/app/controllers/application_controller.rb
231
+ - test/dummy/app/helpers/application_helper.rb
232
+ - test/dummy/app/views/layouts/application.html.erb
260
233
  - test/dummy/public/404.html
234
+ - test/dummy/public/javascripts/prototype.js
261
235
  - test/dummy/public/javascripts/controls.js
236
+ - test/dummy/public/javascripts/effects.js
237
+ - test/dummy/public/javascripts/dragdrop.js
262
238
  - test/dummy/public/javascripts/application.js
263
239
  - test/dummy/public/javascripts/rails.js
264
- - test/dummy/public/javascripts/dragdrop.js
265
- - test/dummy/public/javascripts/prototype.js
266
- - test/dummy/public/javascripts/effects.js
267
- - test/dummy/app/controllers/application_controller.rb
268
- - test/dummy/app/views/layouts/application.html.erb
269
- - test/dummy/app/helpers/application_helper.rb
270
- - test/unit/test_widget_shortcuts.rb
271
- - test/unit/event_handler_test.rb
272
- - test/unit/widget_shortcuts_test.rb
273
- - test/unit/stateful_widget_test.rb
274
- - test/unit/test_addressing.rb
240
+ - test/dummy/public/422.html
241
+ - test/dummy/public/favicon.ico
242
+ - test/dummy/public/500.html
243
+ - test/dummy/config.ru
244
+ - test/dummy/config/environments/test.rb
245
+ - test/dummy/config/environments/production.rb
246
+ - test/dummy/config/environments/development.rb
247
+ - test/dummy/config/database.yml
248
+ - test/dummy/config/boot.rb
249
+ - test/dummy/config/locales/en.yml
250
+ - test/dummy/config/environment.rb
251
+ - test/dummy/config/initializers/secret_token.rb
252
+ - test/dummy/config/initializers/backtrace_silencers.rb
253
+ - test/dummy/config/initializers/session_store.rb
254
+ - test/dummy/config/initializers/mime_types.rb
255
+ - test/dummy/config/initializers/inflections.rb
256
+ - test/dummy/config/application.rb
257
+ - test/dummy/config/routes.rb
258
+ - test/dummy/db/test.sqlite3
259
+ - test/dummy/script/rails
260
+ - test/support/test_case_methods.rb
261
+ - test/rails/rails_integration_test.rb
262
+ - test/rails/view_methods_test.rb
263
+ - test/rails/controller_methods_test.rb
264
+ - test/rails/view_helper_test.rb
265
+ - test/rails/widget_generator_test.rb
266
+ - test/unit/onfire_integration_test.rb
275
267
  - test/unit/invoke_test.rb
276
- - test/unit/container_test.rb
277
- - test/unit/test_tab_panel.rb
278
- - test/unit/test_jump_to_state.rb
268
+ - test/unit/request_processor_test.rb
279
269
  - test/unit/render_test.rb
270
+ - test/unit/test_case_test.rb
280
271
  - test/unit/apotomo_test.rb
281
- - test/unit/request_processor_test.rb
282
- - test/unit/test_caching.rb
283
272
  - test/unit/javascript_generator_test.rb
284
- - test/unit/onfire_integration_test.rb
273
+ - test/unit/stateful_widget_test.rb
274
+ - test/unit/event_methods_test.rb
285
275
  - test/unit/persistence_test.rb
276
+ - test/unit/event_handler_test.rb
286
277
  - test/unit/transition_test.rb
287
278
  - test/unit/event_test.rb
288
- - test/unit/event_methods_test.rb
289
279
  - test/unit/widget_test.rb
280
+ - test/unit/widget_shortcuts_test.rb
281
+ - test/unit/test_tab_panel.rb
282
+ - test/unit/test_addressing.rb
283
+ - test/unit/container_test.rb
284
+ - test/unit/test_jump_to_state.rb
@@ -1,37 +0,0 @@
1
- # Introduces caching of rendered state views into the StatefulWidget.
2
- module Apotomo::Caching
3
-
4
- def self.included(base) #:nodoc:
5
- base.class_eval do
6
- extend ClassMethods
7
- end
8
- end
9
-
10
-
11
- module ClassMethods
12
- # If <tt>version_proc</tt> is omitted, Apotomo provides some basic caching
13
- # mechanism: the state view rendered for <tt>state</tt> will be cached as long
14
- # as you (or e.g. an EventHandler) calls #dirty!. It will then be re-rendered
15
- # and cached again.
16
- # You may override that to provide fine-grained caching, with multiple cache versions
17
- # for the same state.
18
- def cache(state, version_proc=:cache_version)
19
- super(state, version_proc)
20
- end
21
- end
22
-
23
- def cache_version
24
- @version ||= 0
25
- {:v => @version}
26
- end
27
-
28
- def increment_version
29
- @version += 1
30
- end
31
-
32
- # Instruct caching to re-render all cached state views.
33
- def dirty!
34
- increment_version
35
- end
36
-
37
- end
@@ -1,90 +0,0 @@
1
- module Apotomo
2
- module DeepLinkMethods
3
- def self.included(base)
4
- base.initialize_hooks << :initialize_deep_link_for
5
- end
6
-
7
-
8
- # Called in StatefulWidget's constructor.
9
- def initialize_deep_link_for(id, start_states, opts)
10
- #add_deep_link if opts[:is_url_listener] ### DISCUSS: remove #add_de
11
- end
12
-
13
- def responds_to_url_change?
14
- evt_table.all_handlers_for(:urlChange, name).size > 0
15
- end
16
-
17
-
18
- ### DISCUSS: private? rename to compute_url_fragment_for ?
19
- # Computes the fragment part of the widget's url by querying all widgets up to root.
20
- # Widgets managing a certain state will usually insert state recovery information
21
- # via local_fragment.
22
- def url_fragment_for(local_portion=nil, portions=[])
23
- local_portion = local_fragment if responds_to_url_change? and local_portion.nil?
24
-
25
- portions.unshift(local_portion) # prepend portions as we move up.
26
-
27
- return portions.compact.join("/") if root?
28
-
29
- parent.url_fragment_for(nil, portions)
30
- end
31
-
32
-
33
- # Called when widget :is_url_listener. Adds the local url fragment portion to the url.
34
- def local_fragment
35
- #"#{local_fragment_key}=#{state_name}"
36
- end
37
-
38
- # Key found in the url fragment, pointing to the local fragment.
39
- #def local_fragment_key
40
- # name
41
- #end
42
-
43
-
44
- # Called by DeepLinkWidget#process to query if we're involved in an URL change.
45
- # Do return false if you're not interested in the change.
46
- #
47
- # This especially means:
48
- # * the fragment doesn't include you or is empty
49
- # fragment[name].blank?
50
- # * your portion in the fragment didn't change
51
- # tab=first/content=html vs. tab=first/content=markdown
52
- # fragment[:tab] != @active_tab
53
- def responds_to_url_change_for?(fragment)
54
- end
55
-
56
-
57
-
58
- class UrlFragment
59
- attr_reader :fragment
60
-
61
- def initialize(fragment)
62
- @fragment = fragment || ""
63
- end
64
-
65
- def to_s
66
- fragment.to_s
67
- end
68
-
69
- def blank?
70
- fragment.blank?
71
- end
72
-
73
- ### TODO: make path separator configurable.
74
- def [](key)
75
- if path_portion = fragment.split("/").find {|i| i.include?(key.to_s)}
76
- return path_portion.sub("#{key}=", "")
77
- end
78
-
79
- nil
80
- end
81
- end
82
-
83
- # Query object for the url fragment. Use this to retrieve state information from the
84
- # deep link.
85
- def url_fragment
86
- UrlFragment.new(param(:deep_link))
87
- end
88
-
89
- end
90
- end
@@ -1,53 +0,0 @@
1
- require 'test_helper'
2
-
3
- class ApotomoCachingTest < Test::Unit::TestCase
4
- include Apotomo::UnitTestCase
5
-
6
- def setup
7
- super
8
- @controller.session= {}
9
- @cc = CachingCell.new('caching_cell', :start)
10
- @cc.controller = @controller
11
- end
12
-
13
-
14
- def test_caching_with_instance_version_proc
15
- unless ActionController::Base.cache_configured?
16
- throw Exception.new "cache_configured? returned false. You may enable caching in your config/environments/test.rb to make this test pass."
17
- return
18
- end
19
- c1 = @cc.invoke
20
- c2 = @cc.invoke
21
- assert_equal c1, c2
22
-
23
- @cc.dirty!
24
-
25
- c3 = @cc.invoke
26
- assert c2 != c3
27
- end
28
-
29
- end
30
-
31
-
32
- class CachingCell < Apotomo::StatefulWidget
33
-
34
- cache :cached_state
35
-
36
- transition :in => :cached_state
37
-
38
-
39
- def start
40
- jump_to_state :cached_state
41
- end
42
-
43
- def cached_state
44
- @counter ||= 0
45
- @counter += 1
46
- "#{@counter}"
47
-
48
- end
49
-
50
- def not_cached_state
51
- "i'm really static"
52
- end
53
- end
@@ -1,44 +0,0 @@
1
- require 'test_helper'
2
-
3
- # fixture:
4
- module My
5
- class TestCell < Apotomo::StatefulWidget
6
- def a_state
7
- "a_state"
8
- end
9
- end
10
-
11
- class TestWidget < Apotomo::StatefulWidget
12
- def a_state
13
- "a_state"
14
- end
15
- end
16
- end
17
-
18
- class MyTestWidgetTree < Apotomo::WidgetTree
19
- def draw(root)
20
- root << widget('apotomo/stateful_widget', :widget_content, 'widget_one')
21
- root << cell(:my_test, :a_state, 'my_test_cell')
22
- root << switch('my_switch') << widget('apotomo/stateful_widget', :widget_content, :child_widget)
23
- root << section('my_section')
24
- root << widget('apotomo/stateful_widget', :widget_content, :widget_three)
25
- #root ### FIXME! find a way to return nothing by default.
26
- end
27
- end
28
-
29
-
30
- class WidgetShortcutsTest < Test::Unit::TestCase
31
- include Apotomo::UnitTestCase
32
-
33
-
34
- def test_cell
35
- assert_kind_of My::TestCell, cell("my/test", :a_state, 'my_test_cell')
36
- end
37
-
38
- def test_widget
39
- w = widget("my/test_widget", :a_state, 'my_test_cell')
40
- assert_kind_of My::TestWidget, w
41
- assert_equal "my_test_cell", w.name
42
- end
43
-
44
- end