observables 0.1.1 → 0.1.2
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.
- 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
|
+
|