apotomo 1.0.0.beta2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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