rrx 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reactive::Observable do
4
+ include Lets
5
+
6
+ context 'timer(2000).push(timer(5000))' do
7
+ before do
8
+ pending
9
+ o1 = Reactive::Observable.timer(2000)
10
+ o2 = Reactive::Observable.timer(5000)
11
+ @s = o1.push(o2)
12
+ subscribe(@s)
13
+ end
14
+
15
+ it 'does not fire before first timer' do
16
+ observer[:on_next].should_not_receive(:call)
17
+ advance_by(1000)
18
+ end
19
+
20
+ it 'fires once after first timer runs' do
21
+ observer[:on_next].should_receive(:call).with(0)
22
+ advance_by(2001)
23
+ end
24
+
25
+ it 'fires once after first but before the pushed one' do
26
+ observer[:on_next].should_receive(:call).with(0)
27
+ advance_by(4001)
28
+ end
29
+
30
+ it 'fires twice once both timers have run' do
31
+ observer[:on_next].should_receive(:call).exactly(:twice).with(0)
32
+ advance_by(7001)
33
+ end
34
+
35
+ it 'doesn\'t fire any more after both timers have run' do
36
+ observer[:on_next].should_receive(:call).exactly(:twice).with(0)
37
+ advance_by(20001)
38
+ end
39
+
40
+ end
41
+
42
+ context 'interval(100).push(interval(222))' do
43
+ before do
44
+ o1 = Reactive::Observable.interval(100)
45
+ o2 = Reactive::Observable.interval(222)
46
+ @s = o1.push(o2)
47
+ @s.subscribe(observer)
48
+ end
49
+
50
+ it 'doesn\'t fire on partial interval' do
51
+ observer[:on_next].should_not_receive(:call)
52
+ advance_by(99)
53
+ end
54
+
55
+ it 'fires once when first interval passed' do
56
+ observer[:on_next].should_receive(:call).with(0)
57
+ advance_by(101)
58
+ end
59
+
60
+ it 'fires for pushed interval, but not for original interval' do
61
+ observer[:on_next].should_receive(:call).exactly(:twice).with(anything)
62
+ advance_by(230)
63
+ end
64
+ end
65
+
66
+
67
+ context 'when garbage collected' do
68
+ before do
69
+ o1 = Reactive::Observable.interval(100)
70
+ o2 = Reactive::Observable.interval(222)
71
+ @s = o1.push(o2)
72
+ @s.subscribe(observer)
73
+ remove_instance_variable(:@s)
74
+ ObjectSpace.garbage_collect
75
+ end
76
+ it 'unsubscribes' do
77
+ scheduler.advance_by(230)
78
+ end
79
+ end
80
+
81
+
82
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reactive::Observable do
4
+ include Lets
5
+
6
+ context '#skip(3) on list(4..0)' do
7
+
8
+ before do
9
+ @s = Reactive::Observable.from_array([4,3,2,1,0]).skip(3)
10
+ end
11
+
12
+ #TODO send_call..with should accept arguments for each time. in this case 1, 0
13
+ it {
14
+ observer[:on_complete] = ->() {}
15
+ should send_call.exactly(2).times.with(kind_of(Fixnum)).to(observer, :on_next)
16
+ }
17
+ it {
18
+ observer[:on_next] = ->(v) {}
19
+ should send_call.with(no_args).to(observer, :on_complete)
20
+ }
21
+
22
+ after do
23
+ @s.subscribe(observer)
24
+ end
25
+
26
+
27
+ end
28
+
29
+
30
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reactive::Observable do
4
+ include Lets
5
+
6
+ context '#timer(2000)' do
7
+ before do
8
+ @s = Reactive::Observable.timer(2000)
9
+ @s.subscribe(observer)
10
+ end
11
+
12
+ #TODO shared example?
13
+ it 'doesn\'t fire on subscribe' do
14
+ observer[:on_next].should_not_receive(:call)
15
+ end
16
+
17
+ it "doesn't fire before timer" do
18
+ observer[:on_next].should_not_receive(:call)
19
+ end
20
+
21
+ context 'timer has passed' do
22
+ advance_by(2001, ignore: :on_complete) {
23
+ should send_call.with(0).to(observer, :on_next)
24
+ }
25
+ advance_by(2001, ignore: :on_next) {
26
+ should send_call.to(observer, :on_complete)
27
+ }
28
+ end
29
+
30
+ context "timer x2 has passed" do
31
+ advance_by(4001, ignore: :on_complete) {
32
+ should send_call.with(0).to(observer, :on_next)
33
+ }
34
+ advance_by(4001, ignore: :on_next) {
35
+ should send_call.to(observer, :on_complete)
36
+ }
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,177 @@
1
+ require 'rrx'
2
+
3
+
4
+ class VirtualScheduler
5
+ attr_accessor :now
6
+
7
+ def initialize
8
+ restart
9
+ end
10
+
11
+ def schedule_periodic(at, action, wrapper = Reactive::Disposable::Wrapper.new)
12
+ return unless wrapper
13
+ callback = lambda do
14
+ new_at = action.()
15
+ if new_at #defined ~
16
+ schedule_periodic(new_at, action, wrapper)
17
+ else
18
+ wrapper.unwrap
19
+ end
20
+ end
21
+ wrapper.target = schedule_once(at, callback)
22
+ end
23
+
24
+ def schedule_once(at, action)
25
+ add_action(@now + at, action)
26
+ Reactive::Disposable::Closure.new { remove_action(action) }
27
+ end
28
+
29
+ def add_action(at, action)
30
+ @actions << [at, action]
31
+ end
32
+
33
+ def remove_action(action)
34
+ @actions.reject! {|at_ac| at_ac[1] == action }
35
+ end
36
+
37
+ def sort_actions
38
+ @actions.sort_by! {|at_ac| at_ac[0] }
39
+ end
40
+
41
+ def advance_by(ms)
42
+ max = @now + ms
43
+ while true
44
+ sort_actions
45
+ t, action = @actions[0]
46
+ break if t.nil?
47
+ break if t > max
48
+ @now = t
49
+ remove_action(action)
50
+ action.call
51
+ end
52
+ @now = max
53
+ end
54
+
55
+ def restart
56
+ @actions = []
57
+ @now = 0
58
+ end
59
+
60
+ end
61
+
62
+ class SendMessage
63
+ def initialize(method)
64
+ @method = method
65
+ end
66
+
67
+ def with(args)
68
+ @with = args
69
+ self
70
+ end
71
+
72
+ def exactly(times)
73
+ @exactly = times
74
+ self
75
+ end
76
+
77
+ def times
78
+ self
79
+ end
80
+
81
+ def to(object, attr)
82
+ @target = object[attr]
83
+ @name = attr
84
+ m = @target.should_receive(@method)
85
+ m = m.exactly(@exactly).times if @exactly
86
+ m.with(@with) if @with
87
+ self
88
+ end
89
+
90
+ def matches?(that)
91
+ true
92
+ end
93
+
94
+ def description
95
+ "call #{@name}#{ " with #{@with}" if @with }#{ " #{@exactly} times" if @exactly}"
96
+ end
97
+
98
+ end
99
+
100
+ def send_call()
101
+ SendMessage.new(:call)
102
+ end
103
+
104
+
105
+ #def error
106
+ # @error ||= []
107
+ #end
108
+
109
+ def restart
110
+ scheduler.restart
111
+ end
112
+
113
+ def advance_and_check_event_counts(events)
114
+ events.each {|event| advance_and_check_event_count(*event) }
115
+ end
116
+
117
+ def advance_and_check_event_count(advance_by = nil, e_nxt = 0, e_complete = 0, e_error = 0, opts= {})
118
+ [[:next_size, e_nxt], [:complete_size, e_complete], [:error_size, e_error]].each do |base, chg|
119
+ it do
120
+ expect {
121
+ instance_eval(&opts[:proc]) if opts[:proc]
122
+ scheduler.advance_by(advance_by)
123
+ }.to change(self, base).by(chg)
124
+ end
125
+ end
126
+ #[nxt.size, error.size, complete.size].should == [e_nxt, e_error, e_complete]
127
+ end
128
+
129
+ def next_size
130
+ nxt.size
131
+ end
132
+ def error_size
133
+ error.size
134
+ end
135
+ def complete_size
136
+ complete.size
137
+ end
138
+
139
+
140
+
141
+ def subscribe(observable)
142
+ observable.subscribe(
143
+ :on_next => ->(v) { nxt << v; },
144
+ :on_complete => ->() { complete << 1 },
145
+ :on_error => ->(e) { error << e }
146
+ )
147
+ end
148
+
149
+
150
+ def advance_by(ms, options = {}, &block)
151
+ if block
152
+ case options[:ignore]
153
+ when :on_next, :on_error
154
+ before { observer[options[:ignore]] = ->(v) {} }
155
+ when :on_complete
156
+ before { observer[:on_complete] = ->() {} }
157
+ end
158
+ after {
159
+ scheduler.advance_by(ms)
160
+ }
161
+ it &block
162
+ else
163
+ scheduler.advance_by(ms)
164
+ end
165
+ end
166
+
167
+ module Lets
168
+ def self.included(context)
169
+ context.let(:scheduler) { VirtualScheduler.new }
170
+ #context.let(:observer) { {on_next: "Double (next)", on_error: double('error'), on_complete: double('complete') } }
171
+ context.let(:observer) { {on_next: double('next'), on_error: double('error'), on_complete: double('complete') } }
172
+
173
+ context.before do
174
+ Reactive::Observable::Base.any_instance.stub(:scheduler).and_return(scheduler)
175
+ end
176
+ end
177
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rrx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Daniel Davis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: eventmachine
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: active_support
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: ! 'Reactive Extensions provide an enumerable-like interface to process
79
+ temporal data(events) with the eash usually reserved for structural Enumerables '
80
+ email:
81
+ - shoefish@gmail.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - examples/all.rb
92
+ - lib/Reactive/observable.rb
93
+ - lib/Reactive/observable/base.rb
94
+ - lib/Reactive/observable/composite.rb
95
+ - lib/Reactive/observable/double_wrapper.rb
96
+ - lib/Reactive/observable/empty.rb
97
+ - lib/Reactive/observable/first.rb
98
+ - lib/Reactive/observable/from_proc.rb
99
+ - lib/Reactive/observable/generate.rb
100
+ - lib/Reactive/observable/grep.rb
101
+ - lib/Reactive/observable/map.rb
102
+ - lib/Reactive/observable/merge.rb
103
+ - lib/Reactive/observable/push.rb
104
+ - lib/Reactive/observable/skip.rb
105
+ - lib/Reactive/observable/wrapper.rb
106
+ - lib/Reactive/observer.rb
107
+ - lib/Reactive/scheduler.rb
108
+ - lib/Reactive/version.rb
109
+ - lib/rrx.rb
110
+ - rrx.gemspec
111
+ - spec/observable/count_spec.rb
112
+ - spec/observable/first_spec.rb
113
+ - spec/observable/interval_spec.rb
114
+ - spec/observable/push_spec.rb
115
+ - spec/observable/skip_spec.rb
116
+ - spec/observable/timer_spec.rb
117
+ - spec/spec_helper.rb
118
+ homepage: https://github.com/emirikol/rrx
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.24
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: Ruby Reactive Extensions
142
+ test_files:
143
+ - spec/observable/count_spec.rb
144
+ - spec/observable/first_spec.rb
145
+ - spec/observable/interval_spec.rb
146
+ - spec/observable/push_spec.rb
147
+ - spec/observable/skip_spec.rb
148
+ - spec/observable/timer_spec.rb
149
+ - spec/spec_helper.rb