apotomo 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,20 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class ContainerTest < Test::Unit::TestCase
4
+ context "Rendering a container" do
5
+ setup do
6
+ @family = container('family')
7
+ @family.controller = @controller
8
+ end
9
+
10
+ should "return an empty view if childless" do
11
+ assert_equal "<div id=\"family\"></div>", @family.invoke
12
+ end
13
+
14
+ should "provide a family picture" do
15
+ @family << mouse_mock('mum')
16
+ @family << mouse_mock('kid')
17
+ assert_equal "<div id=\"family\"><div id=\"mum\">burp!</div>\n<div id=\"kid\">burp!</div></div>", @family.invoke
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+
4
+ class EventHandlerTest < Test::Unit::TestCase
5
+ context "an abstract EventHandler" do
6
+ should "push nil to root's ordered page_updates when #call'ed" do
7
+ @mum = mouse_mock('mum')
8
+ @mum << @kid = mouse_mock('kid')
9
+
10
+ assert_equal 0, @mum.page_updates.size
11
+
12
+ [@mum, @kid, @mum].each do |source|
13
+ Apotomo::EventHandler.new.call(Apotomo::Event.new(:squeak, source))
14
+ end
15
+
16
+ # order matters:
17
+ assert_equal 3, @mum.page_updates.size
18
+ assert_equal 0, @kid.page_updates.size
19
+ assert_equal(nil, @mum.page_updates[0])
20
+ assert_equal(nil, @mum.page_updates[1])
21
+ assert_equal(nil, @mum.page_updates[2])
22
+ end
23
+ end
24
+
25
+
26
+
27
+ def test_invoke_to_s
28
+ h = Apotomo::InvokeEventHandler.new
29
+ h.widget_id = :widget_id
30
+ h.state = :my_state
31
+ assert_equal "InvokeEventHandler:widget_id#my_state", h.to_s
32
+ end
33
+
34
+ def test_proc_to_s
35
+ h = Apotomo::ProcEventHandler.new
36
+ h.proc = :my_method
37
+ assert_equal "ProcEventHandler:my_method", h.to_s
38
+ end
39
+
40
+ def test_constructor_for_proc
41
+ h = Apotomo::ProcEventHandler.new
42
+ assert_nil h.proc
43
+ h = Apotomo::ProcEventHandler.new(:proc => :method)
44
+ assert_equal :method, h.proc
45
+ end
46
+
47
+ def test_constructor_for_invoke
48
+ h = Apotomo::InvokeEventHandler.new
49
+ assert_nil h.widget_id
50
+ assert_nil h.state
51
+ h = Apotomo::InvokeEventHandler.new(:widget_id => :widget, :state => :state)
52
+ assert_equal :widget, h.widget_id
53
+ assert_equal :state, h.state
54
+ end
55
+
56
+ def test_equal
57
+ h1 = Apotomo::ProcEventHandler.new(:proc => :run)
58
+ h2 = Apotomo::ProcEventHandler.new(:proc => :run)
59
+ h3 = Apotomo::ProcEventHandler.new(:proc => :walk)
60
+
61
+ assert h1 == h2
62
+ assert h1 != h3
63
+ end
64
+
65
+ ### TODO: test #call
66
+
67
+ end
@@ -0,0 +1,83 @@
1
+ require File.join(File.dirname(__FILE__), %w(.. test_helper))
2
+
3
+ class EventMethodsTest < Test::Unit::TestCase
4
+
5
+ context "#respond_to_event and #fire" do
6
+ setup do
7
+ mum_and_kid!
8
+ end
9
+
10
+ should "alert @mum first, then make her squeak when @kid squeaks" do
11
+ @kid.fire :squeak
12
+ assert_equal ['be alerted', 'answer squeak'], @mum.list
13
+ end
14
+
15
+ should "make @mum just squeak back when @jerry squeaks" do
16
+ @mum << @jerry = mouse_mock('jerry')
17
+ @jerry.fire :squeak
18
+ assert_equal ['answer squeak'], @mum.list
19
+ end
20
+
21
+
22
+ should "make @mum run away while @kid keeps watching" do
23
+ @kid.fire :footsteps
24
+ assert_equal ['peek', 'escape'], @mum.list
25
+ end
26
+
27
+ should "by default add a handler only once" do
28
+ @mum.respond_to_event :peep, :with => :answer_squeak
29
+ @mum.respond_to_event :peep, :with => :answer_squeak
30
+ @mum.fire :peep
31
+ assert_equal ['answer squeak'], @mum.list
32
+ end
33
+
34
+ should "squeak back twice when using the :once => false option" do
35
+ @mum.respond_to_event :peep, :with => :answer_squeak
36
+ @mum.respond_to_event :peep, :with => :answer_squeak, :once => false
37
+ @mum.fire :peep
38
+ assert_equal ['answer squeak', 'answer squeak'], @mum.list
39
+ end
40
+
41
+ context "#responds_to_event in class context" do
42
+ setup do
43
+ class AdultMouseCell < MouseCell
44
+ responds_to_event :peep, :with => :answer_squeak
45
+ end
46
+ class BabyMouseCell < AdultMouseCell
47
+ responds_to_event :footsteps, :with => :squeak
48
+ end
49
+ end
50
+
51
+ should "add the handlers at creation time" do
52
+ assert_equal [Apotomo::InvokeEventHandler.new(:widget_id => 'mum', :state => :answer_squeak)], AdultMouseCell.new('mum', :show).event_table.all_handlers_for(:peep, 'mum')
53
+ end
54
+
55
+ should "not inherit handlers for now" do
56
+ assert_equal [], BabyMouseCell.new('kid', :show).event_table.all_handlers_for(:peep, 'kid')
57
+ end
58
+ end
59
+
60
+
61
+ context "#trigger" do
62
+ should "be an alias for #fire" do
63
+ @kid.trigger :footsteps
64
+ assert_equal ['peek', 'escape'], @mum.list
65
+ end
66
+ end
67
+
68
+
69
+ context "page_updates" do
70
+ should "expose a simple Array for now" do
71
+ assert_kind_of Array, @mum.page_updates
72
+ assert_equal 0, @mum.page_updates.size
73
+ end
74
+
75
+ should "be queued in root#page_updates after #fire" do
76
+ @mum.fire :footsteps
77
+ assert_equal ["escape"], @mum.page_updates
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class EventTest < Test::Unit::TestCase
4
+ context "An Event" do
5
+ should "require type and respond to #type" do
6
+ assert_equal :footsteps, Apotomo::Event.new(:footsteps).type
7
+ end
8
+
9
+ should "require source and respond to #source" do
10
+ @event = Apotomo::Event.new(:footsteps, 'mum')
11
+ assert_equal :footsteps, @event.type
12
+ assert_equal 'mum', @event.source
13
+ end
14
+
15
+ should "accept an additional data object and respond to #data" do
16
+ @event = Apotomo::Event.new(:footsteps, 'mum', {:volume => :loud})
17
+ assert_equal({:volume => :loud}, @event.data)
18
+ end
19
+
20
+ should "complain when serialized" do
21
+ assert_raises RuntimeError do
22
+ Marshal.dump(Apotomo::Event.new(:footsteps, 'mum'))
23
+ end
24
+ end
25
+
26
+ should "respond to #stopped?" do
27
+ assert_not Apotomo::Event.new(:footsteps, 'mum').stopped?
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,123 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class InvokeTest < Test::Unit::TestCase
4
+ class LocalMouse < MouseCell
5
+ def snuggle; render; end
6
+ def educate; render :view => :snuggle; end
7
+ end
8
+
9
+ context "Invoking a single widget" do
10
+ setup do
11
+ @mum = LocalMouse.new('mum', :snuggle)
12
+ end
13
+
14
+ context "implicitely" do
15
+ should "always enter the given state" do
16
+ @mum.invoke :snuggle
17
+ assert_equal :snuggle, @mum.last_state
18
+
19
+ @mum.invoke :educate
20
+ assert_equal :educate, @mum.last_state
21
+ end
22
+ end
23
+
24
+ context "explicitely" do
25
+ should "per default enter the start state" do
26
+ @mum.invoke
27
+ assert_equal :snuggle, @mum.last_state
28
+
29
+ @mum.invoke
30
+ assert_equal :snuggle, @mum.last_state
31
+ end
32
+
33
+ context "with defined transitions" do
34
+ setup do
35
+ @mum.instance_eval do
36
+ self.class.transition :from => :snuggle, :to => :educate
37
+ end
38
+
39
+ @mum.invoke
40
+ assert_equal :snuggle, @mum.last_state
41
+ end
42
+
43
+ should "automatically follow the transitions if defined" do
44
+ @mum.invoke
45
+ assert_equal :educate, @mum.last_state
46
+ end
47
+
48
+ should "nevertheless allow undefined implicit invokes" do
49
+ @mum.invoke :snuggle
50
+ assert_equal :snuggle, @mum.last_state
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ context "Invoking a widget family" do
57
+ setup do
58
+ @mum = LocalMouse.new('mum', :snuggle)
59
+
60
+ # create an anonym class for @kid so we don't pollute with #transition's.
61
+ @mum << @kid = mouse_class_mock.new('kid', :snooze)
62
+ @kid.instance_eval do
63
+ def snooze; render :nothing => true; end
64
+ def listen; render :nothing => true; end
65
+ end
66
+ end
67
+
68
+ context "implicitely" do
69
+ should "per default send kid to its start state" do
70
+ @mum.invoke :snuggle
71
+ assert_equal :snuggle, @mum.last_state
72
+ assert_equal :snooze, @kid.last_state
73
+
74
+ @mum.invoke :educate
75
+ assert_equal :educate, @mum.last_state
76
+ assert_equal :snooze, @kid.last_state
77
+ end
78
+
79
+ should "follow the kid's transition if defined" do
80
+ @kid.instance_eval do
81
+ self.class.transition :from => :snooze, :to => :listen
82
+ end
83
+
84
+ @mum.invoke :snuggle
85
+ @mum.invoke :educate
86
+ assert_equal :educate, @mum.last_state
87
+ assert_equal :listen, @kid.last_state
88
+ end
89
+
90
+ should "send kid to the given state passed to #render" do
91
+ @mum.instance_eval do
92
+ def snuggle
93
+ render :invoke => {'kid' => :listen}
94
+ end
95
+ end
96
+
97
+ @mum.invoke :snuggle
98
+ assert_equal :snuggle, @mum.last_state
99
+ assert_equal :listen, @kid.last_state
100
+ end
101
+
102
+ should "send kid to the :invoke state as it overrides #transition" do
103
+ @kid.instance_eval do
104
+ self.class.transition :from => :snooze, :to => :listen
105
+ end
106
+
107
+ @mum.instance_eval do
108
+ def educate
109
+ render :nothing => true, :invoke => {'kid' => :snooze}
110
+ end
111
+ end
112
+
113
+ @mum.invoke :snuggle
114
+ assert_equal :snuggle, @mum.last_state
115
+ assert_equal :snooze, @kid.last_state
116
+
117
+ @mum.invoke :educate
118
+ assert_equal :educate, @mum.last_state
119
+ assert_equal :snooze, @kid.last_state
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,90 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+
4
+ class JavascriptGeneratorTest < Test::Unit::TestCase
5
+ context "The JavascriptGenerator" do
6
+ should "raise an error if no framework passed" do
7
+ assert_raises RuntimeError do
8
+ Apotomo::JavascriptGenerator.new(nil)
9
+ end
10
+ end
11
+
12
+ context "in prototype mode" do
13
+ setup do
14
+ @gen = Apotomo::JavascriptGenerator.new(:prototype)
15
+ end
16
+
17
+ should "respond to prototype" do
18
+ assert_respond_to @gen, :prototype
19
+ end
20
+
21
+ should "respond to xhr" do
22
+ assert_equal "new Ajax.Request(\"/drink/beer?source=nick\")", @gen.xhr('/drink/beer?source=nick')
23
+ end
24
+
25
+ should "respond to replace" do
26
+ assert_equal "$(\"drinks\").replace(\"EMPTY!\")", @gen.replace(:drinks, 'EMPTY!')
27
+ end
28
+
29
+ should "respond to update" do
30
+ assert_equal "$(\"drinks\").update(\"<li id=\\\"beer\\\"><\\/li>\")", @gen.update(:drinks, '<li id="beer"></li>')
31
+ end
32
+
33
+ should "respond to <<" do
34
+ assert_equal "alert(\"Beer!\")", @gen << 'alert("Beer!")'
35
+ end
36
+ end
37
+
38
+ context "in right mode" do
39
+ setup do
40
+ @gen = Apotomo::JavascriptGenerator.new(:right)
41
+ end
42
+
43
+ should "respond to right" do
44
+ assert_respond_to @gen, :right
45
+ end
46
+
47
+ should "respond to xhr" do
48
+ assert_equal "new Xhr(\"/drink/beer?source=nick\", {evalScripts:true}).send()", @gen.xhr('/drink/beer?source=nick')
49
+ end
50
+
51
+ should "respond to replace" do
52
+ assert_equal "$(\"drinks\").replace(\"EMPTY!\")", @gen.replace(:drinks, 'EMPTY!')
53
+ end
54
+
55
+ should "respond to update" do
56
+ assert_equal "$(\"drinks\").update(\"<li id=\\\"beer\\\"><\\/li>\")", @gen.update(:drinks, '<li id="beer"></li>')
57
+ end
58
+
59
+ should "respond to <<" do
60
+ assert_equal "alert(\"Beer!\")", @gen << 'alert("Beer!")'
61
+ end
62
+ end
63
+
64
+ context "in jquery mode" do
65
+ setup do
66
+ @gen = Apotomo::JavascriptGenerator.new(:jquery)
67
+ end
68
+
69
+ should "respond to jquery" do
70
+ assert_respond_to @gen, :jquery
71
+ end
72
+
73
+ should "respond to xhr" do
74
+ assert_equal "$.ajax({url: \"/drink/beer?source=nick\"})", @gen.xhr('/drink/beer?source=nick')
75
+ end
76
+
77
+ should "respond to replace" do
78
+ assert_equal "$(\"#drinks\").replaceWith(\"EMPTY!\")", @gen.replace(:drinks, 'EMPTY!')
79
+ end
80
+
81
+ should "respond to update" do
82
+ assert_equal "$(\"#drinks\").html(\"<li id=\\\"beer\\\"><\\/li>\")", @gen.update(:drinks, '<li id="beer"></li>')
83
+ end
84
+
85
+ should "respond to <<" do
86
+ assert_equal "alert(\"Beer!\")", @gen << 'alert("Beer!")'
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class OnfireIntegrationTest < Test::Unit::TestCase
4
+ context "including Onfire into the StatefulWidget it" do
5
+ setup do
6
+ @mum = mouse_mock('mum')
7
+ @mum << @kid = mouse_mock('kid')
8
+ end
9
+
10
+ should "respond to #root" do
11
+ assert @mum.root?
12
+ assert ! @kid.root?
13
+ end
14
+
15
+ should "respond to #parent" do
16
+ assert_equal @mum, @kid.parent
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,240 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class PersistenceTest < Test::Unit::TestCase
4
+
5
+ class PersistentMouse < Apotomo::StatefulWidget # we need a named class for marshalling.
6
+ attr_reader :who, :what
7
+
8
+ def educate
9
+ @who = "the cat"
10
+ @what = "run away"
11
+ render :nothing => true
12
+ end
13
+
14
+ def recap; render :nothing => true; end
15
+ end
16
+
17
+ context "StatefulWidget" do
18
+
19
+ context ".stateful_branches_for" do
20
+ should "provide all stateful branch-roots seen from root" do
21
+ @root = Apotomo::Widget.new('root', :eat)
22
+ @root << mum_and_kid!
23
+ @root << Apotomo::Widget.new('berry', :eat) << @jerry = mouse_mock('jerry', :eat)
24
+
25
+ assert_equal ['mum', 'jerry'], Apotomo::StatefulWidget.stateful_branches_for(@root).collect {|n| n.name}
26
+ end
27
+ end
28
+ end
29
+
30
+ context "After #hibernate_widget (request) the widget" do
31
+ should "still have the same ivars" do
32
+ @mum = PersistentMouse.new('mum', :educate)
33
+ @mum.controller = @controller ### FIXME: remove that dependency
34
+
35
+ @mum.invoke(:educate)
36
+
37
+ assert_equal @mum.last_state, :educate
38
+ assert_equal @mum.who, "the cat"
39
+ assert_equal @mum.what, "run away"
40
+
41
+ @mum = hibernate_widget(@mum)
42
+ @mum.controller = @controller ### FIXME: remove that dependency
43
+
44
+ @mum.invoke(:recap)
45
+
46
+ assert_equal @mum.last_state, :recap
47
+ assert_equal @mum.who, "the cat"
48
+ assert_equal @mum.what, "run away"
49
+ end
50
+
51
+ should "still have its event_table" do
52
+ @mum = PersistentMouse.new('mum', :educate)
53
+ @event = Apotomo::Event.new(:squeak, @mum)
54
+ @mum.respond_to_event :squeak, :with => :educate
55
+
56
+ assert_equal 1, @mum.send(:local_event_handlers, @event).size
57
+ @mum = hibernate_widget(@mum)
58
+ assert_equal 1, @mum.send(:local_event_handlers, @event).size
59
+ end
60
+ end
61
+
62
+ context "freezing and thawing a widget family" do
63
+ setup do
64
+ mum_and_kid!
65
+ @storage = {}
66
+ end
67
+
68
+ context "and calling #flush_storage" do
69
+ should "clear the storage from frozen data" do
70
+ @root = Apotomo::Widget.new('root', :eat)
71
+ @root << @mum
72
+
73
+ Apotomo::StatefulWidget.freeze_for(@storage, @root)
74
+
75
+ assert @storage[:apotomo_stateful_branches]
76
+ assert @storage[:apotomo_widget_ivars]
77
+
78
+ Apotomo::StatefulWidget.flush_storage(@storage)
79
+
80
+ assert_nil @storage[:apotomo_stateful_branches]
81
+ assert_nil @storage[:apotomo_widget_ivars]
82
+ end
83
+ end
84
+
85
+ should "push @mum's freezable ivars to the storage when calling #freeze_ivars_to" do
86
+ @mum.freeze_ivars_to(@storage)
87
+
88
+ assert_equal 1, @storage.size
89
+ assert_equal 5, @storage['mum'].size
90
+ end
91
+
92
+ should "push family's freezable ivars to the storage when calling #freeze_data_to" do
93
+ @kid << mouse_mock('pet')
94
+ @mum.freeze_data_to(@storage)
95
+
96
+ assert_equal 3, @storage.size
97
+ assert_equal 5, @storage['mum'].size
98
+ assert_equal 5, @storage['mum/kid'].size
99
+ assert_equal 4, @storage['mum/kid/pet'].size
100
+ end
101
+
102
+ should "push ivars and structure to the storage when calling #freeze_to" do
103
+ @mum.freeze_to(@storage)
104
+ assert_equal 2, @storage[:apotomo_widget_ivars].size
105
+ assert_kind_of Apotomo::StatefulWidget, @storage[:apotomo_root]
106
+ end
107
+
108
+ context "that has also stateless widgets" do
109
+ setup do
110
+ @root = Apotomo::Widget.new('root', :eat)
111
+ @root << mum_and_kid!
112
+ @root << Apotomo::Widget.new('berry', :eat) << @jerry = mouse_mock('jerry', :eat)
113
+ @root << Apotomo::Widget.new('tom', :eating)
114
+
115
+ Apotomo::StatefulWidget.freeze_for(@storage, @root)
116
+ end
117
+
118
+ should "ignore stateless widgets when calling #freeze_for" do
119
+ assert_equal(['root/mum', 'root/mum/kid', "root/berry/jerry"], @storage[:apotomo_widget_ivars].keys)
120
+ end
121
+
122
+ should "save stateful branches only" do
123
+ #@mum.root!
124
+ #@jerry.root! # disconnect stateful branches.
125
+
126
+ assert_equal([[@mum, 'root'], [@jerry, 'berry']], @storage[:apotomo_stateful_branches])
127
+ assert @storage[:apotomo_stateful_branches].first.first.root?, "mum not disconnected from root"
128
+ assert @storage[:apotomo_stateful_branches].last.first.root?, "jerry not disconnected from berry"
129
+ end
130
+
131
+ should "attach stateful branches to the tree in thaw_for" do
132
+ @new_root = Apotomo::Widget.new('root', :eat)
133
+ @new_root << Apotomo::Widget.new('berry', :eat)
134
+ assert_equal @new_root, Apotomo::StatefulWidget.thaw_for(@storage, @new_root)
135
+
136
+ assert_equal 5, @new_root.size # without tom.
137
+ end
138
+
139
+ should "re-establish ivars recursivly when calling #thaw_for" do
140
+ @storage[:apotomo_stateful_branches] = Marshal.load(Marshal.dump(@storage[:apotomo_stateful_branches]))
141
+
142
+ @new_root = Apotomo::Widget.new('root', :eat)
143
+ @new_root << Apotomo::Widget.new('berry', :eat)
144
+ @new_root = Apotomo::StatefulWidget.thaw_for(@storage, @new_root)
145
+
146
+ assert_equal :answer_squeak, @new_root['mum'].instance_variable_get(:@start_state)
147
+ assert_equal :peek, @new_root['mum']['kid'].instance_variable_get(:@start_state)
148
+ end
149
+
150
+ should "raise an exception when thaw_for can't find the branch's parent" do
151
+ @new_root = Apotomo::Widget.new('dad', :eat)
152
+
153
+ assert_raises RuntimeError do
154
+ Apotomo::StatefulWidget.thaw_for(@storage, @new_root)
155
+ end
156
+ end
157
+
158
+ should "clear the fields in the storage when fetching in #thaw_for" do
159
+ @new_root = Apotomo::Widget.new('root', :eat)
160
+ @new_root << Apotomo::Widget.new('berry', :eat)
161
+
162
+ Apotomo::StatefulWidget.thaw_for(@storage, @new_root)
163
+
164
+ assert_nil @storage[:apotomo_stateful_branches]
165
+ assert_nil @storage[:apotomo_widget_ivars]
166
+ end
167
+ end
168
+
169
+ should "update @mum's ivars when calling #thaw_ivars_from" do
170
+ @mum.instance_variable_set(:@name, "zombie mum")
171
+ assert_equal 'zombie mum', @mum.name
172
+
173
+ @mum.thaw_ivars_from({'zombie mum' => {'@name' => 'mum'}})
174
+ assert_equal 'mum', @mum.name
175
+ end
176
+
177
+ should "update family's ivars when calling #thaw_data_from" do
178
+ @kid << @pet = mouse_mock('pet')
179
+ @kid.instance_variable_set(:@name, "paranoid kid")
180
+ @pet.instance_variable_set(:@name, "mad dog")
181
+ assert_equal "paranoid kid", @kid.name
182
+
183
+ @mum.thaw_data_from({ "mum/paranoid kid" => {'@name' => 'kid'},
184
+ "mum/kid/mad dog" => {'@name' => 'pet'}})
185
+ assert_equal 'kid', @kid.name
186
+ assert_equal 'pet', @pet.name
187
+ end
188
+
189
+
190
+ end
191
+
192
+ context "dumping and loading" do
193
+ setup do
194
+ @mum = PersistentMouse.new('mum', :eating)
195
+ @mum << @kid = PersistentMouse.new('kid', :eating)
196
+ end
197
+
198
+ context "a single stateful widget" do
199
+ should "provide a serialized widget on #node_dump" do
200
+ assert_equal "mum|PersistenceTest::PersistentMouse|mum", @mum.dump_node
201
+ assert_equal "kid|PersistenceTest::PersistentMouse|mum", @kid.dump_node
202
+ end
203
+
204
+ should "recover the widget skeleton when invoking self.node_load" do
205
+ @mum, parent = ::Apotomo::StatefulWidget.load_node(@mum.dump_node)
206
+ assert_kind_of PersistentMouse, @mum
207
+ assert_equal 'mum', @mum.name
208
+ assert_equal 1, @mum.size
209
+ assert_equal 'mum', parent
210
+ end
211
+ end
212
+
213
+ context "a stateful widget family" do
214
+ should "provide the serialized tree on _dump" do
215
+ assert_equal "mum|PersistenceTest::PersistentMouse|mum\nkid|PersistenceTest::PersistentMouse|mum\n", @mum._dump(10)
216
+ end
217
+
218
+ should "recover the widget tree on _load" do
219
+ @mum = ::Apotomo::StatefulWidget._load(@mum._dump(10))
220
+ assert_equal 2, @mum.size
221
+ assert_equal @mum, @mum['kid'].parent
222
+ end
223
+ end
224
+ end
225
+
226
+ context "#frozen_widget_in?" do
227
+ should "return true if a valid widget is passed" do
228
+ assert_not Apotomo::StatefulWidget.frozen_widget_in?({})
229
+ assert Apotomo::StatefulWidget.frozen_widget_in?({:apotomo_stateful_branches => [[mouse_mock, 'root']]})
230
+ end
231
+ end
232
+
233
+ context "#symbolized_instance_variables?" do
234
+ should "return instance_variables as symbols" do
235
+ @mum = mouse_mock
236
+ assert_equal @mum.instance_variables.size, @mum.symbolized_instance_variables.size
237
+ assert_not @mum.symbolized_instance_variables.find { |ivar| ivar.kind_of? String }
238
+ end
239
+ end
240
+ end