rrx 0.0.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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +7 -0
- data/examples/all.rb +36 -0
- data/lib/Reactive/observable.rb +58 -0
- data/lib/Reactive/observable/base.rb +88 -0
- data/lib/Reactive/observable/composite.rb +34 -0
- data/lib/Reactive/observable/double_wrapper.rb +17 -0
- data/lib/Reactive/observable/empty.rb +7 -0
- data/lib/Reactive/observable/first.rb +26 -0
- data/lib/Reactive/observable/from_proc.rb +17 -0
- data/lib/Reactive/observable/generate.rb +41 -0
- data/lib/Reactive/observable/grep.rb +35 -0
- data/lib/Reactive/observable/map.rb +20 -0
- data/lib/Reactive/observable/merge.rb +26 -0
- data/lib/Reactive/observable/push.rb +47 -0
- data/lib/Reactive/observable/skip.rb +25 -0
- data/lib/Reactive/observable/wrapper.rb +10 -0
- data/lib/Reactive/observer.rb +36 -0
- data/lib/Reactive/scheduler.rb +23 -0
- data/lib/Reactive/version.rb +3 -0
- data/lib/rrx.rb +125 -0
- data/rrx.gemspec +25 -0
- data/spec/observable/count_spec.rb +30 -0
- data/spec/observable/first_spec.rb +47 -0
- data/spec/observable/interval_spec.rb +47 -0
- data/spec/observable/push_spec.rb +82 -0
- data/spec/observable/skip_spec.rb +30 -0
- data/spec/observable/timer_spec.rb +41 -0
- data/spec/spec_helper.rb +177 -0
- metadata +149 -0
|
@@ -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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|