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,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