apotomo 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/Gemfile +10 -0
  2. data/Gemfile.lock +47 -0
  3. data/README +141 -0
  4. data/README.rdoc +141 -0
  5. data/Rakefile +78 -0
  6. data/TODO +36 -0
  7. data/app/cells/apotomo/child_switch_widget/switch.html.erb +1 -0
  8. data/app/cells/apotomo/child_switch_widget/switch.rhtml +1 -0
  9. data/app/cells/apotomo/deep_link_widget.rb +27 -0
  10. data/app/cells/apotomo/deep_link_widget/setup.html.erb +20 -0
  11. data/app/cells/apotomo/java_script_widget.rb +12 -0
  12. data/app/cells/apotomo/tab_panel_widget.rb +87 -0
  13. data/app/cells/apotomo/tab_panel_widget/display.html.erb +57 -0
  14. data/app/cells/apotomo/tab_widget.rb +18 -0
  15. data/app/cells/apotomo/tab_widget/display.html.erb +1 -0
  16. data/config/routes.rb +3 -0
  17. data/generators/widget/USAGE +15 -0
  18. data/generators/widget/templates/functional_test.rb +8 -0
  19. data/generators/widget/templates/view.html.erb +2 -0
  20. data/generators/widget/templates/view.html.haml +3 -0
  21. data/generators/widget/templates/widget.rb +8 -0
  22. data/generators/widget/widget_generator.rb +34 -0
  23. data/lib/apotomo.rb +59 -0
  24. data/lib/apotomo/caching.rb +37 -0
  25. data/lib/apotomo/container_widget.rb +10 -0
  26. data/lib/apotomo/deep_link_methods.rb +90 -0
  27. data/lib/apotomo/event.rb +9 -0
  28. data/lib/apotomo/event_handler.rb +23 -0
  29. data/lib/apotomo/event_methods.rb +102 -0
  30. data/lib/apotomo/invoke_event_handler.rb +24 -0
  31. data/lib/apotomo/javascript_generator.rb +57 -0
  32. data/lib/apotomo/persistence.rb +139 -0
  33. data/lib/apotomo/proc_event_handler.rb +18 -0
  34. data/lib/apotomo/rails/controller_methods.rb +161 -0
  35. data/lib/apotomo/rails/view_helper.rb +95 -0
  36. data/lib/apotomo/rails/view_methods.rb +7 -0
  37. data/lib/apotomo/request_processor.rb +92 -0
  38. data/lib/apotomo/stateful_widget.rb +8 -0
  39. data/lib/apotomo/transition.rb +46 -0
  40. data/lib/apotomo/tree_node.rb +186 -0
  41. data/lib/apotomo/version.rb +5 -0
  42. data/lib/apotomo/widget.rb +289 -0
  43. data/lib/apotomo/widget_shortcuts.rb +36 -0
  44. data/rails/init.rb +0 -0
  45. data/test/fixtures/application_widget_tree.rb +2 -0
  46. data/test/rails/controller_methods_test.rb +206 -0
  47. data/test/rails/rails_integration_test.rb +99 -0
  48. data/test/rails/view_helper_test.rb +77 -0
  49. data/test/rails/view_methods_test.rb +40 -0
  50. data/test/rails/widget_generator_test.rb +47 -0
  51. data/test/support/assertions_helper.rb +13 -0
  52. data/test/support/test_case_methods.rb +68 -0
  53. data/test/test_helper.rb +77 -0
  54. data/test/unit/apotomo_test.rb +20 -0
  55. data/test/unit/container_test.rb +20 -0
  56. data/test/unit/event_handler_test.rb +67 -0
  57. data/test/unit/event_methods_test.rb +83 -0
  58. data/test/unit/event_test.rb +30 -0
  59. data/test/unit/invoke_test.rb +123 -0
  60. data/test/unit/javascript_generator_test.rb +90 -0
  61. data/test/unit/onfire_integration_test.rb +19 -0
  62. data/test/unit/persistence_test.rb +240 -0
  63. data/test/unit/render_test.rb +203 -0
  64. data/test/unit/request_processor_test.rb +178 -0
  65. data/test/unit/stateful_widget_test.rb +135 -0
  66. data/test/unit/test_addressing.rb +111 -0
  67. data/test/unit/test_caching.rb +54 -0
  68. data/test/unit/test_jump_to_state.rb +89 -0
  69. data/test/unit/test_tab_panel.rb +72 -0
  70. data/test/unit/test_widget_shortcuts.rb +45 -0
  71. data/test/unit/transition_test.rb +33 -0
  72. data/test/unit/widget_shortcuts_test.rb +68 -0
  73. data/test/unit/widget_test.rb +24 -0
  74. metadata +215 -0
@@ -0,0 +1,203 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class RenderTest < ActionView::TestCase
4
+ context "Rendering a single widget" do
5
+ setup do
6
+ @mum = mouse_mock
7
+ end
8
+
9
+ should "per default display the state content framed in a div" do
10
+ assert_equal '<div id="mouse">burp!</div>', @mum.invoke(:eating)
11
+ end
12
+
13
+ context "with :text" do
14
+ setup do
15
+ @mum.instance_eval { def eating; render :text => "burp!!!"; end }
16
+ end
17
+
18
+ should "render the :text" do
19
+ assert_equal "burp!!!", @mum.invoke
20
+ end
21
+ end
22
+
23
+
24
+ context "with :suppress_js" do
25
+ setup do
26
+ @mum.instance_eval do
27
+ def snuggle; render; end
28
+ self.class.send :attr_reader, :suppress_js
29
+ end
30
+ end
31
+
32
+ should "per default be false" do
33
+ @mum.invoke :snuggle
34
+ assert !@mum.suppress_js
35
+ end
36
+
37
+ should "be true when set" do
38
+ @mum.instance_eval do
39
+ def snuggle; render :suppress_js => true; end
40
+ end
41
+ @mum.invoke :snuggle
42
+ assert @mum.suppress_js
43
+ end
44
+ end
45
+
46
+ should "expose its instance variables in the rendered view" do
47
+ @mum = mouse_mock('mum', :educate) do
48
+ def educate
49
+ @who = "the cat"
50
+ @what = "run away"
51
+ render
52
+ end
53
+ end
54
+ assert_equal 'If you see the cat do run away!', @mum.invoke(:educate)
55
+ end
56
+
57
+ context "with #emit" do
58
+ setup do
59
+ @kid = mouse_mock('kid', :squeak)
60
+ @kid.instance_eval do
61
+ def squeak
62
+ render :text => "squeeeeaaak"
63
+ end
64
+
65
+ def render(*)
66
+ @rendered = true
67
+ super
68
+ end
69
+ def rendered?; @rendered; end
70
+ end
71
+ end
72
+
73
+ context "and :text" do
74
+ setup do
75
+ @mum.instance_eval do
76
+ def squeak
77
+ emit :text => "squeak();"
78
+ end
79
+ end
80
+ end
81
+
82
+ should "just return the plain :text" do
83
+ assert_equal 'squeak();', @mum.invoke(:squeak)
84
+ end
85
+
86
+ should "not render children" do
87
+ @mum << @kid
88
+ @mum.invoke(:squeak)
89
+
90
+ assert_not @kid.rendered?
91
+ end
92
+
93
+ should "allow rendering children" do
94
+ @mum.instance_eval do
95
+ def squeak
96
+ emit :text => "squeak();", :render_children => true
97
+ end
98
+ end
99
+ @mum << @kid
100
+ @mum.invoke(:squeak)
101
+
102
+ assert @kid.rendered?
103
+ end
104
+ end
105
+
106
+ context "and no options" do
107
+ setup do
108
+ @mum.instance_eval do
109
+ def squeak
110
+ emit
111
+ end
112
+ end
113
+ end
114
+
115
+ should "render the view" do
116
+ assert_equal "<div id=\"mouse\">burp!</div>", @mum.invoke(:eating)
117
+ end
118
+
119
+ should "render the children, too" do
120
+ @mum << @kid
121
+ @mum.invoke(:eating)
122
+ assert @kid.rendered?
123
+ end
124
+ end
125
+
126
+ context "and :view" do
127
+ setup do
128
+ @mum.instance_eval do
129
+ def squeak
130
+ emit :view => :snuggle
131
+ end
132
+ end
133
+ end
134
+
135
+ should "render the :view" do
136
+ assert_equal "<div id=\"mouse\"><snuggle></snuggle></div>", @mum.invoke(:squeak)
137
+ end
138
+
139
+ should "render the children" do
140
+ @mum << @kid
141
+
142
+ assert_equal "<div id=\"mouse\"><snuggle>squeeeeaaak</snuggle></div>", @mum.invoke(:squeak)
143
+ assert @kid.rendered?
144
+ end
145
+ end
146
+ end
147
+
148
+ context "with #update" do
149
+ setup do
150
+ Apotomo.js_framework = :prototype
151
+ end
152
+
153
+ should "wrap the :text in an update statement" do
154
+ @mum.instance_eval do
155
+ def squeak
156
+ update :text => "squeak!"
157
+ end
158
+ end
159
+ assert_equal "$(\"mouse\").update(\"squeak!\")", @mum.invoke(:squeak)
160
+ end
161
+ end
162
+
163
+ context "with #replace" do
164
+ setup do
165
+ Apotomo.js_framework = :prototype
166
+ end
167
+
168
+ should "wrap the :text in a replace statement" do
169
+ @mum.instance_eval do
170
+ def squeak
171
+ replace :text => '<div id="mum">squeak!</div>'
172
+ end
173
+ end
174
+ assert_equal "$(\"mouse\").replace(\"<div id=\\\"mum\\\">squeak!<\\/div>\")", @mum.invoke(:squeak)
175
+ end
176
+ end
177
+ end
178
+
179
+ context "rendering a widget family" do
180
+ setup do
181
+ @mum = mouse_mock('mum', :snuggle) do
182
+ def snuggle; render; end
183
+ end
184
+
185
+ @mum << @kid = mouse_mock('kid')
186
+ end
187
+
188
+ should "per default render kid's content inside mums div with rendered_children" do
189
+ assert_equal '<div id="mum"><snuggle><div id="kid">burp!</div></snuggle></div>', @mum.invoke(:snuggle)
190
+ end
191
+
192
+ should "skip kids if :render_children=>false but still provide a rendered_children hash" do
193
+ @mum.instance_eval do
194
+ def snuggle; render :render_children => false; end
195
+ end
196
+
197
+ assert_equal '<div id="mum"><snuggle></snuggle></div>', @mum.invoke(:snuggle)
198
+ end
199
+
200
+ should_eventually "provide an ordered rendered_children hash"
201
+ end
202
+
203
+ end
@@ -0,0 +1,178 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class RequestProcessorTest < Test::Unit::TestCase
4
+ context "#root" do
5
+ should "allow external modification of the tree" do
6
+ @processor = Apotomo::RequestProcessor.new({})
7
+ root = @processor.root
8
+ root << mouse_mock
9
+ assert_equal 2, @processor.root.size
10
+ end
11
+ end
12
+
13
+ context "option processing at construction time" do
14
+ context "with empty session and options" do
15
+ setup do
16
+ @processor = Apotomo::RequestProcessor.new({})
17
+ end
18
+
19
+ should "mark the tree as flushed" do
20
+ assert @processor.widgets_flushed?
21
+ end
22
+
23
+ should "provide a single root-node for #root" do
24
+ assert_equal 1, @processor.root.size
25
+ end
26
+
27
+ should "initialize version to 0" do
28
+ assert_equal 0, @processor.root.version
29
+ end
30
+ end
31
+
32
+ context "with session" do
33
+ setup do
34
+ mum_and_kid!
35
+ @mum.version = 1
36
+ @session = {:apotomo_stateful_branches => [[@mum, 'root']]}
37
+ @processor = Apotomo::RequestProcessor.new(@session)
38
+ end
39
+
40
+ should "provide a widget family for #root" do
41
+ assert_equal 3, @processor.root.size
42
+ assert_equal 1, @processor.root['mum'].version
43
+ assert_not @processor.widgets_flushed?
44
+ end
45
+
46
+ context "having a flush flag set" do
47
+ setup do
48
+ @processor = Apotomo::RequestProcessor.new(@session, :flush_widgets => true)
49
+ end
50
+
51
+ should "provide a single root for #root when :flush_widgets is set" do
52
+ assert_equal 1, @processor.root.size
53
+ assert @processor.widgets_flushed?
54
+ end
55
+
56
+ should "wipe-out our session variables" do
57
+ assert_nil @session[:apotomo_stateful_branches]
58
+ assert_nil @session[:apotomo_widget_ivars]
59
+ end
60
+
61
+ should ""
62
+ end
63
+
64
+ context "and with stateless widgets" do
65
+ setup do
66
+ @session = {:apotomo_stateful_branches => [[@mum, 'grandma']]}
67
+ @processor = Apotomo::RequestProcessor.new(@session, {}, [Proc.new { |root| root << Apotomo::Widget.new('grandma', :eating) }])
68
+ end
69
+
70
+ should "first attach passed stateless, then stateful widgets to root" do
71
+ assert_equal 4, @processor.root.size
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ context "#process_for" do
79
+ setup do
80
+ ### FIXME: what about that automatic @controller everywhere?
81
+ mum_and_kid!
82
+ @mum.controller = nil # check if controller gets connected.
83
+ @processor = Apotomo::RequestProcessor.new({:apotomo_stateful_branches => [[@mum, 'root']]}, :js_framework => :prototype)
84
+
85
+
86
+
87
+
88
+
89
+ @kid.respond_to_event :doorSlam, :with => :eating, :on => 'mum'
90
+ @kid.respond_to_event :doorSlam, :with => :squeak
91
+ @mum.respond_to_event :doorSlam, :with => :squeak
92
+
93
+ @mum.instance_eval do
94
+ def squeak; render :js => 'squeak();'; end
95
+ end
96
+ @kid.instance_eval do
97
+ def squeak; render :text => 'squeak!', :update => :true; end
98
+ end
99
+ end
100
+
101
+ should "return 2 page_updates when @kid squeaks" do
102
+ res = @processor.process_for({:type => :squeak, :source => 'kid'}, @controller)
103
+
104
+ assert_equal ["alert!", "squeak"], res
105
+ end
106
+
107
+ should "raise an exception when :source is unknown" do
108
+ assert_raises RuntimeError do
109
+ @processor.process_for({:type => :squeak, :source => 'tom'}, @controller)
110
+ end
111
+ end
112
+ end
113
+
114
+
115
+
116
+ context "#freeze!" do
117
+ should "serialize stateful branches to @session" do
118
+ @processor = Apotomo::RequestProcessor.new({})
119
+ @processor.root << mum_and_kid!
120
+ assert_equal 3, @processor.root.size
121
+ @processor.freeze!
122
+
123
+ @processor = Apotomo::RequestProcessor.new(@processor.session)
124
+ assert_equal 3, @processor.root.size
125
+ end
126
+ end
127
+
128
+ context "#render_widget_for" do
129
+ setup do
130
+ @mum = mouse_mock('mum', :snuggle) do
131
+ def snuggle; render; end
132
+ end
133
+ @mum.controller = nil
134
+
135
+ @processor = Apotomo::RequestProcessor.new({:apotomo_stateful_branches => [[@mum, 'root']]})
136
+ end
137
+
138
+ should "render the widget when passing an existing widget id" do
139
+ assert_equal '<div id="mum"><snuggle></snuggle></div>', @processor.render_widget_for('mum', {}, @controller)
140
+ end
141
+
142
+ should "render the widget when passing an existing widget instance" do
143
+ assert_equal '<div id="mum"><snuggle></snuggle></div>', @processor.render_widget_for(@mum, {}, @controller)
144
+ end
145
+
146
+ should "raise an exception when a non-existent widget id id passed" do
147
+ assert_raises RuntimeError do
148
+ @processor.render_widget_for('mummy', {}, @controller)
149
+ end
150
+ end
151
+ end
152
+
153
+ context "invoking #address_for" do
154
+ setup do
155
+ @processor = Apotomo::RequestProcessor.new({})
156
+ end
157
+
158
+ should "accept an event :type" do
159
+ assert_equal({:type => :squeak, :source => 'mum'}, @processor.address_for(:type => :squeak, :source => 'mum'))
160
+ end
161
+
162
+ should "accept arbitrary options" do
163
+ assert_equal({:type => :squeak, :volume => 'loud', :source => 'mum'}, @processor.address_for(:type => :squeak, :volume => 'loud', :source => 'mum'))
164
+ end
165
+
166
+ should "complain if no type given" do
167
+ assert_raises RuntimeError do
168
+ @processor.address_for(:source => 'mum')
169
+ end
170
+ end
171
+
172
+ should "complain if no source given" do
173
+ assert_raises RuntimeError do
174
+ @processor.address_for(:type => :footsteps)
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,135 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class StatefulWidgetTest < Test::Unit::TestCase
4
+ context "The StatefulWidget" do
5
+ setup do
6
+ @mum = Apotomo::StatefulWidget.new('mum', :squeak)
7
+ end
8
+
9
+ should "accept an id as first option" do
10
+ assert_equal 'mum', @mum.name
11
+ end
12
+
13
+ should "accept a start state as second option" do
14
+ assert_equal :squeak, @mum.instance_variable_get('@start_state')
15
+ end
16
+
17
+ should "respond to #version" do
18
+ assert_equal 0, mouse_mock.version
19
+ end
20
+
21
+ should "have a version setter" do
22
+ @mum = mouse_mock
23
+ @mum.version = 1
24
+ assert_equal 1, @mum.version
25
+ end
26
+
27
+ context "responding to #address_for_event" do
28
+ should "accept an event :type" do
29
+ assert_equal({:type => :squeak, :source => 'mum'}, @mum.address_for_event(:type => :squeak))
30
+ end
31
+
32
+ should "accept a :source" do
33
+ assert_equal({:type => :squeak, :source => 'kid'}, @mum.address_for_event(:type => :squeak, :source => 'kid'))
34
+ end
35
+
36
+ should "accept arbitrary options" do
37
+ assert_equal({:type => :squeak, :volume => 'loud', :source => 'mum'}, @mum.address_for_event(:type => :squeak, :volume => 'loud'))
38
+ end
39
+
40
+ should "complain if no type given" do
41
+ assert_raises RuntimeError do
42
+ @mum.address_for_event(:source => 'mum')
43
+ end
44
+ end
45
+ end
46
+
47
+ context "implementing visibility" do
48
+ should "per default respond to #visible?" do
49
+ assert @mum.visible?
50
+ end
51
+
52
+ should "expose a setter therefore" do
53
+ @mum.visible = false
54
+ assert_not @mum.visible?
55
+ end
56
+
57
+ context "in a widget family" do
58
+ setup do
59
+ @mum << @jerry = mouse_mock('jerry')
60
+ @mum << @berry = mouse_mock('berry')
61
+ end
62
+
63
+ should "per default return all #visible_children" do
64
+ assert_equal [@jerry, @berry], @mum.visible_children
65
+ assert_equal [], @jerry.visible_children
66
+ end
67
+
68
+ should "hide berry in #visible_children if he's invisible" do
69
+ @berry.visible = false
70
+ assert_equal [@jerry], @mum.visible_children
71
+ end
72
+ end
73
+ end
74
+
75
+ should "respond to #find_widget" do
76
+ mum_and_kid!
77
+ assert_not @mum.find_widget('pet')
78
+ assert @kid, @mum.find_widget('kid')
79
+ end
80
+
81
+ should "respond to the WidgetShortcuts methods, like #widget" do
82
+ assert_respond_to @mum, :widget
83
+ end
84
+
85
+ context "with initialize_hooks" do
86
+ should "expose its class_inheritable_array with #initialize_hooks" do
87
+ @mum = mouse_class_mock.new('mum', :eating)
88
+ @mum.class.instance_eval { self.initialize_hooks << :initialize_mouse }
89
+ assert ::Apotomo::StatefulWidget.initialize_hooks.size + 1 == @mum.class.initialize_hooks.size
90
+ end
91
+
92
+ should "execute the initialize_hooks in the correct order in #process_initialize_hooks" do
93
+ @mum = mouse_class_mock.new('mum', :eating)
94
+ @mum.class.instance_eval do
95
+ define_method(:executed) { |*args| @executed ||= [] }
96
+ define_method(:setup) { |*args| executed << :setup }
97
+ define_method(:configure) { |*args| executed << :configure }
98
+ initialize_hooks << :setup
99
+ initialize_hooks << :configure
100
+ end
101
+
102
+ assert_equal [:setup, :configure], @mum.class.new('zombie', nil).executed
103
+ end
104
+ end
105
+ end
106
+
107
+ context "mum having a family" do
108
+ setup do
109
+ mum_and_kid!
110
+ @mum << @berry = mouse_mock('berry')
111
+ @berry << @pet = mouse_mock('pet')
112
+ end
113
+
114
+ context "responding to #render_children" do
115
+ should "return an OrderedHash for the rendered kids" do
116
+ kids = @mum.render_children
117
+ assert_kind_of ::ActiveSupport::OrderedHash, kids
118
+ assert_equal 2, kids.size
119
+ end
120
+
121
+ should "return an OrderedHash even if there are no kids" do
122
+ kids = @kid.render_children
123
+ assert_kind_of ::ActiveSupport::OrderedHash, kids
124
+ assert_equal 0, kids.size
125
+ end
126
+
127
+ should "return an empty OrderedHash when all kids are invisible" do
128
+ @pet.visible = false
129
+ kids = @berry.render_children
130
+ assert_kind_of ::ActiveSupport::OrderedHash, kids
131
+ assert_equal 0, kids.size
132
+ end
133
+ end
134
+ end
135
+ end