observables 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -20
- data/README.rdoc +117 -117
- data/lib/observables.rb +14 -14
- data/lib/observables/array_watcher.rb +50 -50
- data/lib/observables/base.rb +97 -94
- data/lib/observables/collections.rb +14 -14
- data/lib/observables/extensions/array.rb +4 -4
- data/lib/observables/extensions/hash.rb +4 -4
- data/lib/observables/extensions/object.rb +9 -9
- data/lib/observables/hash_watcher.rb +39 -39
- data/lib/observables/version.rb +4 -4
- data/test/extensions/test_extensions.rb +20 -20
- data/test/{test_observables.rb → test_Observables.rb} +9 -9
- data/test/test_array_watcher.rb +136 -136
- data/test/test_base.rb +126 -126
- data/test/test_hash_watcher.rb +88 -88
- data/test/test_helper.rb +13 -13
- data/test/test_observable_array.rb +12 -12
- metadata +41 -28
data/test/test_base.rb
CHANGED
@@ -1,127 +1,127 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class TestBase < Test::Unit::TestCase
|
4
|
-
|
5
|
-
context "An instance of a class that include Observables:Base" do
|
6
|
-
setup do
|
7
|
-
@obs = Class.new{include Observables::Base}.new
|
8
|
-
end
|
9
|
-
|
10
|
-
should "have a notifier" do
|
11
|
-
assert @obs.notifier.is_a?(ActiveSupport::Notifications::Fanout)
|
12
|
-
end
|
13
|
-
|
14
|
-
should "allow subscriptions with just a block" do
|
15
|
-
assert @obs.subscribe{}.is_a?(ActiveSupport::Notifications::Fanout::Subscriber)
|
16
|
-
end
|
17
|
-
|
18
|
-
should "allow subscriptions with a pattern and a block" do
|
19
|
-
assert @obs.subscribe(/whatever/){}.is_a?(ActiveSupport::Notifications::Fanout::Subscriber)
|
20
|
-
end
|
21
|
-
|
22
|
-
should "allow unsubscribing" do
|
23
|
-
sub = @obs.subscribe(/hi/){}
|
24
|
-
assert @obs.notifier.listening?("hi")
|
25
|
-
@obs.unsubscribe(sub)
|
26
|
-
assert_equal false, @obs.notifier.listening?("hi")
|
27
|
-
end
|
28
|
-
|
29
|
-
should "publish a before notification prior to executing a change" do
|
30
|
-
vals = []
|
31
|
-
x = 0
|
32
|
-
@obs.subscribe(/before/){|c,a|vals << c << a << x}
|
33
|
-
@obs.send(:changing,:a_change,:this=>:that){x+=1}
|
34
|
-
assert_equal :before_a_change, vals[0]
|
35
|
-
assert_equal @obs.send(:create_event_args,:a_change,:this=>:that),vals[1]
|
36
|
-
assert_equal 0, vals[2]
|
37
|
-
assert_equal 1, x
|
38
|
-
end
|
39
|
-
|
40
|
-
should "publish an after notification after executing a change" do
|
41
|
-
vals = []
|
42
|
-
x = 0
|
43
|
-
@obs.subscribe(/after/){|c,a|vals << c << a << x}
|
44
|
-
@obs.send(:changing,:a_change,:this=>:that){x+=1}
|
45
|
-
assert_equal :after_a_change, vals[0]
|
46
|
-
assert_equal @obs.send(:create_event_args,:a_change,:this=>:that),vals[1]
|
47
|
-
assert_equal 1, vals[2]
|
48
|
-
assert_equal 1, x
|
49
|
-
end
|
50
|
-
|
51
|
-
should "execute a proc passed in as changes to the event args" do
|
52
|
-
vals = []
|
53
|
-
@obs.subscribe(/after/){|_,a|vals << a.changes}
|
54
|
-
@obs.send(:changing, :a_change, :changes=>lambda {[1,2,3]}){1==1}
|
55
|
-
assert_equal [1,2,3], vals[0]
|
56
|
-
end
|
57
|
-
|
58
|
-
should "provide method level access to change args" do
|
59
|
-
vals = []
|
60
|
-
@obs.subscribe(/after/){|_,a|vals << a.haha}
|
61
|
-
@obs.send(:changing, :a_change, :haha=>"hoho"){1==1}
|
62
|
-
assert_equal "hoho", vals[0]
|
63
|
-
end
|
64
|
-
|
65
|
-
context "Taking ownership of an observable collection" do
|
66
|
-
setup do
|
67
|
-
@owner = Class.new do
|
68
|
-
def child_changed(*args)
|
69
|
-
@changed_args = args
|
70
|
-
end
|
71
|
-
def another_child_changed(*args)
|
72
|
-
@changed_args = args
|
73
|
-
end
|
74
|
-
def changed_args; @changed_args; end
|
75
|
-
end
|
76
|
-
@parent = @owner.new
|
77
|
-
end
|
78
|
-
should "notify the parent via standard callback method" do
|
79
|
-
@obs.set_observer @parent
|
80
|
-
@obs.send(:changing, :a_change){1==1}
|
81
|
-
assert_equal @obs, @parent.changed_args[0]
|
82
|
-
end
|
83
|
-
should "notify the parent via custom callback method when specified" do
|
84
|
-
@obs.set_observer @parent, :callback_method=>:another_child_changed
|
85
|
-
@obs.send(:changing, :a_change) {1==1}
|
86
|
-
assert_equal @obs, @parent.changed_args[0]
|
87
|
-
end
|
88
|
-
should "notify the parent via a block if provided" do
|
89
|
-
changed_args = []
|
90
|
-
@obs.set_observer(@parent) { |obs,*_| changed_args << obs }
|
91
|
-
@obs.send(:changing, :a_change) {1==1}
|
92
|
-
assert_equal @obs, changed_args.pop
|
93
|
-
end
|
94
|
-
should "respect a subscription pattern when notifying the parent" do
|
95
|
-
events = []
|
96
|
-
@obs.set_observer(@parent, :pattern=>/before/){|_,evt,*_| events << evt}
|
97
|
-
@obs.send(:changing,:a_change){1==1}
|
98
|
-
assert_equal 1, events.length
|
99
|
-
assert_equal :before_a_change, events.pop
|
100
|
-
end
|
101
|
-
should "notify the parent via argless block" do
|
102
|
-
events = []
|
103
|
-
@obs.set_observer(@parent, :pattern=>/before/){events << 1}
|
104
|
-
@obs.send(:changing, :a_change){1==1}
|
105
|
-
assert_equal 1, events.length
|
106
|
-
end
|
107
|
-
should "notify via block when no owner is given" do
|
108
|
-
events = []
|
109
|
-
my_ary = [1,2,3]
|
110
|
-
my_ary.make_observable
|
111
|
-
my_ary.set_observer(:pattern=>/before/){events << 1}
|
112
|
-
my_ary << 1
|
113
|
-
assert_equal 1, events.length
|
114
|
-
end
|
115
|
-
should "stop notifying the parent after clear_observer is called" do
|
116
|
-
events = []
|
117
|
-
@obs.set_observer(@parent){|*args|events << args}
|
118
|
-
@obs.send(:changing,:a_change){1==1}
|
119
|
-
assert_equal 2, events.length
|
120
|
-
@obs.clear_observer
|
121
|
-
@obs.send(:changing,:a_change){1==1}
|
122
|
-
assert_equal 2, events.length
|
123
|
-
end
|
124
|
-
|
125
|
-
end
|
126
|
-
end
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestBase < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "An instance of a class that include Observables:Base" do
|
6
|
+
setup do
|
7
|
+
@obs = Class.new{include Observables::Base}.new
|
8
|
+
end
|
9
|
+
|
10
|
+
should "have a notifier" do
|
11
|
+
assert @obs.notifier.is_a?(ActiveSupport::Notifications::Fanout)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "allow subscriptions with just a block" do
|
15
|
+
assert @obs.subscribe{}.is_a?(ActiveSupport::Notifications::Fanout::Subscriber)
|
16
|
+
end
|
17
|
+
|
18
|
+
should "allow subscriptions with a pattern and a block" do
|
19
|
+
assert @obs.subscribe(/whatever/){}.is_a?(ActiveSupport::Notifications::Fanout::Subscriber)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "allow unsubscribing" do
|
23
|
+
sub = @obs.subscribe(/hi/){}
|
24
|
+
assert @obs.notifier.listening?("hi")
|
25
|
+
@obs.unsubscribe(sub)
|
26
|
+
assert_equal false, @obs.notifier.listening?("hi")
|
27
|
+
end
|
28
|
+
|
29
|
+
should "publish a before notification prior to executing a change" do
|
30
|
+
vals = []
|
31
|
+
x = 0
|
32
|
+
@obs.subscribe(/before/){|c,a|vals << c << a << x}
|
33
|
+
@obs.send(:changing,:a_change,:this=>:that){x+=1}
|
34
|
+
assert_equal :before_a_change, vals[0]
|
35
|
+
assert_equal @obs.send(:create_event_args,:a_change,:this=>:that),vals[1]
|
36
|
+
assert_equal 0, vals[2]
|
37
|
+
assert_equal 1, x
|
38
|
+
end
|
39
|
+
|
40
|
+
should "publish an after notification after executing a change" do
|
41
|
+
vals = []
|
42
|
+
x = 0
|
43
|
+
@obs.subscribe(/after/){|c,a|vals << c << a << x}
|
44
|
+
@obs.send(:changing,:a_change,:this=>:that){x+=1}
|
45
|
+
assert_equal :after_a_change, vals[0]
|
46
|
+
assert_equal @obs.send(:create_event_args,:a_change,:this=>:that),vals[1]
|
47
|
+
assert_equal 1, vals[2]
|
48
|
+
assert_equal 1, x
|
49
|
+
end
|
50
|
+
|
51
|
+
should "execute a proc passed in as changes to the event args" do
|
52
|
+
vals = []
|
53
|
+
@obs.subscribe(/after/){|_,a|vals << a.changes}
|
54
|
+
@obs.send(:changing, :a_change, :changes=>lambda {[1,2,3]}){1==1}
|
55
|
+
assert_equal [1,2,3], vals[0]
|
56
|
+
end
|
57
|
+
|
58
|
+
should "provide method level access to change args" do
|
59
|
+
vals = []
|
60
|
+
@obs.subscribe(/after/){|_,a|vals << a.haha}
|
61
|
+
@obs.send(:changing, :a_change, :haha=>"hoho"){1==1}
|
62
|
+
assert_equal "hoho", vals[0]
|
63
|
+
end
|
64
|
+
|
65
|
+
context "Taking ownership of an observable collection" do
|
66
|
+
setup do
|
67
|
+
@owner = Class.new do
|
68
|
+
def child_changed(*args)
|
69
|
+
@changed_args = args
|
70
|
+
end
|
71
|
+
def another_child_changed(*args)
|
72
|
+
@changed_args = args
|
73
|
+
end
|
74
|
+
def changed_args; @changed_args; end
|
75
|
+
end
|
76
|
+
@parent = @owner.new
|
77
|
+
end
|
78
|
+
should "notify the parent via standard callback method" do
|
79
|
+
@obs.set_observer @parent
|
80
|
+
@obs.send(:changing, :a_change){1==1}
|
81
|
+
assert_equal @obs, @parent.changed_args[0]
|
82
|
+
end
|
83
|
+
should "notify the parent via custom callback method when specified" do
|
84
|
+
@obs.set_observer @parent, :callback_method=>:another_child_changed
|
85
|
+
@obs.send(:changing, :a_change) {1==1}
|
86
|
+
assert_equal @obs, @parent.changed_args[0]
|
87
|
+
end
|
88
|
+
should "notify the parent via a block if provided" do
|
89
|
+
changed_args = []
|
90
|
+
@obs.set_observer(@parent) { |obs,*_| changed_args << obs }
|
91
|
+
@obs.send(:changing, :a_change) {1==1}
|
92
|
+
assert_equal @obs, changed_args.pop
|
93
|
+
end
|
94
|
+
should "respect a subscription pattern when notifying the parent" do
|
95
|
+
events = []
|
96
|
+
@obs.set_observer(@parent, :pattern=>/before/){|_,evt,*_| events << evt}
|
97
|
+
@obs.send(:changing,:a_change){1==1}
|
98
|
+
assert_equal 1, events.length
|
99
|
+
assert_equal :before_a_change, events.pop
|
100
|
+
end
|
101
|
+
should "notify the parent via argless block" do
|
102
|
+
events = []
|
103
|
+
@obs.set_observer(@parent, :pattern=>/before/){events << 1}
|
104
|
+
@obs.send(:changing, :a_change){1==1}
|
105
|
+
assert_equal 1, events.length
|
106
|
+
end
|
107
|
+
should "notify via block when no owner is given" do
|
108
|
+
events = []
|
109
|
+
my_ary = [1,2,3]
|
110
|
+
my_ary.make_observable
|
111
|
+
my_ary.set_observer(:pattern=>/before/){events << 1}
|
112
|
+
my_ary << 1
|
113
|
+
assert_equal 1, events.length
|
114
|
+
end
|
115
|
+
should "stop notifying the parent after clear_observer is called" do
|
116
|
+
events = []
|
117
|
+
@obs.set_observer(@parent){|*args|events << args}
|
118
|
+
@obs.send(:changing,:a_change){1==1}
|
119
|
+
assert_equal 2, events.length
|
120
|
+
@obs.clear_observer
|
121
|
+
@obs.send(:changing,:a_change){1==1}
|
122
|
+
assert_equal 2, events.length
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
127
|
end
|
data/test/test_hash_watcher.rb
CHANGED
@@ -1,88 +1,88 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class TestHashWatcher < Test::Unit::TestCase
|
4
|
-
context "A hash which has included Observables::HashWatcher" do
|
5
|
-
setup do
|
6
|
-
@hash = {:a=>1,:b=>2,:c=>"3"}.tap do |h|
|
7
|
-
class << h
|
8
|
-
include Observables::HashWatcher
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
should "notify observers of any change that modifies elements" do
|
14
|
-
before_methods, after_methods = [],[]
|
15
|
-
method_list = Observables::HashWatcher::MODIFIER_METHODS
|
16
|
-
@hash.subscribe(/before_modified/){|_,args|before_methods<<args[:trigger]}
|
17
|
-
@hash.subscribe(/after_modified/)
|
18
|
-
method_list.each do |method|
|
19
|
-
args = args_for(method)
|
20
|
-
args ? @hash.send(method,args) : @hash.send(method)
|
21
|
-
end
|
22
|
-
assert_equal method_list, before_methods
|
23
|
-
assert_equal method_list, after_methods
|
24
|
-
end
|
25
|
-
|
26
|
-
should "notify observers of any change that removes elements" do
|
27
|
-
before_methods, after_methods = [],[]
|
28
|
-
method_list = Observables::HashWatcher::REMOVE_METHODS
|
29
|
-
@hash.subscribe(/before_removed/){|_,args|before_methods<<args[:trigger]}
|
30
|
-
@hash.subscribe(/after_removed/) {|_,args|after_methods<<args[:trigger]}
|
31
|
-
method_list.each do |method|
|
32
|
-
args = args_for(method)
|
33
|
-
args ? @hash.send(method,*args) : @hash.send(method)
|
34
|
-
end
|
35
|
-
assert_equal method_list, before_methods
|
36
|
-
assert_equal method_list, after_methods
|
37
|
-
end
|
38
|
-
|
39
|
-
context "Calling #changes on the event args" do
|
40
|
-
should "calculate changes for #[]= as an addition" do
|
41
|
-
assert_equal [[:f,9]],get_changes(@hash){ @hash[:f] = 9}[:added]
|
42
|
-
end
|
43
|
-
should "calculate changes for #[]= as a modification" do
|
44
|
-
assert_equal({:removed=>[[:a,1]],:added=>[[:a,9]]}, get_changes(@hash){@hash[:a]=9})
|
45
|
-
end
|
46
|
-
should "calculate changes for #replace" do
|
47
|
-
assert_equal({:removed=>@hash.dup.to_a,:added=>{:t=>9,:u=>10}.to_a},get_changes(@hash){@hash.replace(:t=>9,:u=>10)})
|
48
|
-
end
|
49
|
-
should "calculate changes for #merge!, #update" do
|
50
|
-
[:merge!, :update].each do |method|
|
51
|
-
hash = @hash.dup
|
52
|
-
assert_equal({:removed=>{:c=>"3"}.to_a, :added=>{:c=>"4",:d=>5}.to_a},get_changes(hash){hash.send(method,{:c=>"4",:d=>5})})
|
53
|
-
end
|
54
|
-
end
|
55
|
-
should "calculate changes for #clear" do
|
56
|
-
assert_equal @hash.to_a
|
57
|
-
end
|
58
|
-
should "calculate changes for #delete" do
|
59
|
-
assert_equal({:a=>1}.to_a, get_changes(@hash){@hash.delete(:a)}[:removed])
|
60
|
-
end
|
61
|
-
should "calculate changes for #delete_if, #reject!" do
|
62
|
-
[:delete_if,:reject!].each do |method|
|
63
|
-
hash = @hash.dup
|
64
|
-
assert_equal({:a=>1,:c=>"3"}.to_a,get_changes(hash){hash.send(method){|k,v|[:a,:c].include?(k)}}[:removed])
|
65
|
-
end
|
66
|
-
end
|
67
|
-
should "calculate changes for #shift" do
|
68
|
-
assert_equal(@hash.dup.shift,get_changes(@hash){@hash.shift}[:removed])
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def args_for(method)
|
74
|
-
case method
|
75
|
-
when :replace, :merge!, :update then {:e=>5}
|
76
|
-
when :delete then :a
|
77
|
-
else nil
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def get_changes(hash)
|
82
|
-
changes = []
|
83
|
-
sub = hash.subscribe(/after/){|_,args|changes << args.changes}
|
84
|
-
yield
|
85
|
-
hash.unsubscribe(sub)
|
86
|
-
changes.pop
|
87
|
-
end
|
88
|
-
end
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class TestHashWatcher < Test::Unit::TestCase
|
4
|
+
context "A hash which has included Observables::HashWatcher" do
|
5
|
+
setup do
|
6
|
+
@hash = {:a=>1,:b=>2,:c=>"3"}.tap do |h|
|
7
|
+
class << h
|
8
|
+
include Observables::HashWatcher
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
should "notify observers of any change that modifies elements" do
|
14
|
+
before_methods, after_methods = [],[]
|
15
|
+
method_list = Observables::HashWatcher::MODIFIER_METHODS
|
16
|
+
@hash.subscribe(/before_modified/){|_,args|before_methods<<args[:trigger]}
|
17
|
+
@hash.subscribe(/after_modified/){|_,args|after_methods<<args[:trigger]}
|
18
|
+
method_list.each do |method|
|
19
|
+
args = args_for(method)
|
20
|
+
args ? @hash.send(method,args) : @hash.send(method)
|
21
|
+
end
|
22
|
+
assert_equal method_list, before_methods
|
23
|
+
assert_equal method_list, after_methods
|
24
|
+
end
|
25
|
+
|
26
|
+
should "notify observers of any change that removes elements" do
|
27
|
+
before_methods, after_methods = [],[]
|
28
|
+
method_list = Observables::HashWatcher::REMOVE_METHODS
|
29
|
+
@hash.subscribe(/before_removed/){|_,args|before_methods<<args[:trigger]}
|
30
|
+
@hash.subscribe(/after_removed/) {|_,args|after_methods<<args[:trigger]}
|
31
|
+
method_list.each do |method|
|
32
|
+
args = args_for(method)
|
33
|
+
args ? @hash.send(method,*args) : @hash.send(method)
|
34
|
+
end
|
35
|
+
assert_equal method_list, before_methods
|
36
|
+
assert_equal method_list, after_methods
|
37
|
+
end
|
38
|
+
|
39
|
+
context "Calling #changes on the event args" do
|
40
|
+
should "calculate changes for #[]= as an addition" do
|
41
|
+
assert_equal [[:f,9]],get_changes(@hash){ @hash[:f] = 9}[:added]
|
42
|
+
end
|
43
|
+
should "calculate changes for #[]= as a modification" do
|
44
|
+
assert_equal({:removed=>[[:a,1]],:added=>[[:a,9]]}, get_changes(@hash){@hash[:a]=9})
|
45
|
+
end
|
46
|
+
should "calculate changes for #replace" do
|
47
|
+
assert_equal({:removed=>@hash.dup.to_a,:added=>{:t=>9,:u=>10}.to_a},get_changes(@hash){@hash.replace(:t=>9,:u=>10)})
|
48
|
+
end
|
49
|
+
should "calculate changes for #merge!, #update" do
|
50
|
+
[:merge!, :update].each do |method|
|
51
|
+
hash = @hash.dup
|
52
|
+
assert_equal({:removed=>{:c=>"3"}.to_a, :added=>{:c=>"4",:d=>5}.to_a},get_changes(hash){hash.send(method,{:c=>"4",:d=>5})})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
should "calculate changes for #clear" do
|
56
|
+
assert_equal [], @hash.to_a - get_changes(@hash){@hash.clear}[:removed]
|
57
|
+
end
|
58
|
+
should "calculate changes for #delete" do
|
59
|
+
assert_equal({:a=>1}.to_a, get_changes(@hash){@hash.delete(:a)}[:removed])
|
60
|
+
end
|
61
|
+
should "calculate changes for #delete_if, #reject!" do
|
62
|
+
[:delete_if,:reject!].each do |method|
|
63
|
+
hash = @hash.dup
|
64
|
+
assert_equal({:a=>1,:c=>"3"}.to_a,get_changes(hash){hash.send(method){|k,v|[:a,:c].include?(k)}}[:removed])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
should "calculate changes for #shift" do
|
68
|
+
assert_equal(@hash.dup.shift,get_changes(@hash){@hash.shift}[:removed])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def args_for(method)
|
74
|
+
case method
|
75
|
+
when :replace, :merge!, :update then {:e=>5}
|
76
|
+
when :delete then :a
|
77
|
+
else nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_changes(hash)
|
82
|
+
changes = []
|
83
|
+
sub = hash.subscribe(/after/){|_,args|changes << args.changes}
|
84
|
+
yield
|
85
|
+
hash.unsubscribe(sub)
|
86
|
+
changes.pop
|
87
|
+
end
|
88
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler/setup'
|
3
|
-
|
4
|
-
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
5
|
-
|
6
|
-
require 'observables'
|
7
|
-
|
8
|
-
require 'shoulda'
|
9
|
-
|
10
|
-
class Test::Unit::TestCase
|
11
|
-
|
12
|
-
end
|
13
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
5
|
+
|
6
|
+
require 'observables'
|
7
|
+
|
8
|
+
require 'shoulda'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
|
12
|
+
end
|
13
|
+
|