reacto 0.0.0
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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +32 -0
- data/README.md +1 -0
- data/lib/reacto/behaviours.rb +50 -0
- data/lib/reacto/constants.rb +7 -0
- data/lib/reacto/executors.rb +31 -0
- data/lib/reacto/operations/buffer.rb +80 -0
- data/lib/reacto/operations/concat.rb +23 -0
- data/lib/reacto/operations/diff.rb +36 -0
- data/lib/reacto/operations/drop.rb +41 -0
- data/lib/reacto/operations/drop_errors.rb +15 -0
- data/lib/reacto/operations/flat_map.rb +26 -0
- data/lib/reacto/operations/flatten.rb +26 -0
- data/lib/reacto/operations/inject.rb +43 -0
- data/lib/reacto/operations/last.rb +31 -0
- data/lib/reacto/operations/map.rb +38 -0
- data/lib/reacto/operations/merge.rb +47 -0
- data/lib/reacto/operations/prepend.rb +19 -0
- data/lib/reacto/operations/select.rb +25 -0
- data/lib/reacto/operations/take.rb +38 -0
- data/lib/reacto/operations/throttle.rb +60 -0
- data/lib/reacto/operations/track_on.rb +18 -0
- data/lib/reacto/operations/uniq.rb +22 -0
- data/lib/reacto/operations.rb +23 -0
- data/lib/reacto/resources/executor_resource.rb +19 -0
- data/lib/reacto/resources.rb +1 -0
- data/lib/reacto/subscriptions/buffered_subscription.rb +52 -0
- data/lib/reacto/subscriptions/combining_last_subscription.rb +22 -0
- data/lib/reacto/subscriptions/combining_subscription.rb +8 -0
- data/lib/reacto/subscriptions/composite_subscription.rb +82 -0
- data/lib/reacto/subscriptions/executor_subscription.rb +63 -0
- data/lib/reacto/subscriptions/flat_map_subscription.rb +20 -0
- data/lib/reacto/subscriptions/inner_subscription.rb +47 -0
- data/lib/reacto/subscriptions/operation_subscription.rb +25 -0
- data/lib/reacto/subscriptions/simple_subscription.rb +80 -0
- data/lib/reacto/subscriptions/subscription.rb +21 -0
- data/lib/reacto/subscriptions/subscription_wrapper.rb +16 -0
- data/lib/reacto/subscriptions/tracker_subscription.rb +76 -0
- data/lib/reacto/subscriptions/zipping_subscription.rb +43 -0
- data/lib/reacto/subscriptions.rb +27 -0
- data/lib/reacto/trackable.rb +317 -0
- data/lib/reacto/tracker.rb +33 -0
- data/lib/reacto/version.rb +4 -0
- data/lib/reacto.rb +10 -0
- data/reacto.gemspec +27 -0
- data/spec/reacto/create_trackable_spec.rb +245 -0
- data/spec/reacto/executors_and_trackable_spec.rb +32 -0
- data/spec/reacto/operations/track_on_spec.rb +20 -0
- data/spec/reacto/trackable/buffer_spec.rb +58 -0
- data/spec/reacto/trackable/throttle_spec.rb +15 -0
- data/spec/reacto/trackable/zip_spec.rb +25 -0
- data/spec/reacto/trackable_spec.rb +417 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/helpers.rb +16 -0
- metadata +150 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'reacto/constants'
|
2
|
+
require 'reacto/subscriptions/composite_subscription'
|
3
|
+
require 'reacto/subscriptions/buffered_subscription'
|
4
|
+
|
5
|
+
module Reacto
|
6
|
+
module Subscriptions
|
7
|
+
class ZippingSubscription < CompositeSubscription
|
8
|
+
def current_value
|
9
|
+
@current_value ||= 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def subscribed?
|
13
|
+
@subscriptions.all? { |s| s.subscribed? }
|
14
|
+
end
|
15
|
+
|
16
|
+
def waiting?
|
17
|
+
@subscriptions.any? { |sub| sub.buffer[current_value] == NO_VALUE }
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_value_subscriptions(_)
|
21
|
+
@subscriber.on_value(
|
22
|
+
@combinator.call(
|
23
|
+
*@subscriptions.map { |sub| sub.buffer[current_value] }
|
24
|
+
)
|
25
|
+
)
|
26
|
+
@current_value += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_close
|
30
|
+
return unless subscribed?
|
31
|
+
@subscriber.on_close
|
32
|
+
end
|
33
|
+
|
34
|
+
def subscription!
|
35
|
+
subscription = BufferedSubscription.new(self)
|
36
|
+
@subscriptions << subscription
|
37
|
+
|
38
|
+
subscription
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'reacto/subscriptions/subscription'
|
2
|
+
require 'reacto/subscriptions/subscription_wrapper'
|
3
|
+
require 'reacto/subscriptions/tracker_subscription'
|
4
|
+
require 'reacto/subscriptions/operation_subscription'
|
5
|
+
require 'reacto/subscriptions/executor_subscription'
|
6
|
+
require 'reacto/subscriptions/simple_subscription'
|
7
|
+
require 'reacto/subscriptions/combining_subscription'
|
8
|
+
require 'reacto/subscriptions/combining_last_subscription'
|
9
|
+
require 'reacto/subscriptions/zipping_subscription'
|
10
|
+
require 'reacto/subscriptions/flat_map_subscription'
|
11
|
+
|
12
|
+
module Reacto
|
13
|
+
module Subscriptions
|
14
|
+
class << self
|
15
|
+
def on_close(&block)
|
16
|
+
SimpleSubscription.new(close: block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_close_and_error(&block)
|
20
|
+
SimpleSubscription.new(
|
21
|
+
close: -> () { block.call },
|
22
|
+
error: -> (_e) { block.call }
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'concurrent'
|
2
|
+
|
3
|
+
require 'reacto/behaviours'
|
4
|
+
require 'reacto/subscriptions'
|
5
|
+
require 'reacto/tracker'
|
6
|
+
require 'reacto/operations'
|
7
|
+
require 'reacto/executors'
|
8
|
+
require 'reacto/resources'
|
9
|
+
|
10
|
+
module Reacto
|
11
|
+
class Trackable
|
12
|
+
TOPICS = [:open, :value, :error, :close]
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def never
|
16
|
+
self.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def combine(*trackables, &block)
|
20
|
+
combine_create(
|
21
|
+
Subscriptions::CombiningSubscription, *trackables, &block
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def combine_last(*trackables, &block)
|
26
|
+
combine_create(
|
27
|
+
Subscriptions::CombiningLastSubscription, *trackables, &block
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def zip(*trackables, &block)
|
32
|
+
combine_create(Subscriptions::ZippingSubscription, *trackables, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def close(executor = nil)
|
36
|
+
make(nil, executor) do |subscriber|
|
37
|
+
subscriber.on_close
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def error(err, executor = nil)
|
42
|
+
make(nil, executor) do |subscriber|
|
43
|
+
subscriber.on_error(err)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def make(behaviour = NO_ACTION, executor = nil, &block)
|
48
|
+
behaviour = block_given? ? block : behaviour
|
49
|
+
self.new(behaviour, executor)
|
50
|
+
end
|
51
|
+
|
52
|
+
def later(secs, value, executor: Reacto::Executors.tasks)
|
53
|
+
if executor.is_a?(Concurrent::ImmediateExecutor)
|
54
|
+
make do |tracker|
|
55
|
+
sleep secs
|
56
|
+
Behaviours.single_tracker_value(tracker, value)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
make do |tracker|
|
60
|
+
Concurrent::ScheduledTask.execute(secs, executor: executor) do
|
61
|
+
Behaviours.single_tracker_value(tracker, value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def interval(
|
68
|
+
interval,
|
69
|
+
enumerator = Behaviours.integers_enumerator,
|
70
|
+
executor: nil
|
71
|
+
)
|
72
|
+
if executor.is_a?(Concurrent::ImmediateExecutor)
|
73
|
+
make do |tracker|
|
74
|
+
Behaviours.with_close_and_error(tracker) do |subscriber|
|
75
|
+
while subscriber.subscribed?
|
76
|
+
sleep interval if subscriber.subscribed?
|
77
|
+
if subscriber.subscribed?
|
78
|
+
begin
|
79
|
+
subscriber.on_value(enumerator.next)
|
80
|
+
rescue StopIteration
|
81
|
+
break
|
82
|
+
end
|
83
|
+
else
|
84
|
+
break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
make do |tracker|
|
91
|
+
queue = Queue.new
|
92
|
+
task = Concurrent::TimerTask.new(execution_interval: interval) do
|
93
|
+
queue.push('ready')
|
94
|
+
end
|
95
|
+
thread = Thread.new do
|
96
|
+
begin
|
97
|
+
loop do
|
98
|
+
queue.pop
|
99
|
+
break unless tracker.subscribed?
|
100
|
+
|
101
|
+
begin
|
102
|
+
value = enumerator.next
|
103
|
+
tracker.on_value(value)
|
104
|
+
rescue StopIteration
|
105
|
+
tracker.on_close if tracker.subscribed?
|
106
|
+
break
|
107
|
+
rescue StandardError => error
|
108
|
+
tracker.on_error(error) if tracker.subscribed?
|
109
|
+
break
|
110
|
+
end
|
111
|
+
end
|
112
|
+
ensure
|
113
|
+
task.shutdown
|
114
|
+
end
|
115
|
+
end
|
116
|
+
task.execute
|
117
|
+
|
118
|
+
tracker.add_resource(Reacto::Resources::ExecutorResource.new(
|
119
|
+
task, threads: [thread]
|
120
|
+
))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def repeat(array, int: 0.1, executor: nil)
|
126
|
+
interval(
|
127
|
+
int, Behaviours.array_repeat_enumerator(array), executor: executor
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
def value(value, executor = nil)
|
132
|
+
make(Behaviours.single_value(value), executor)
|
133
|
+
end
|
134
|
+
|
135
|
+
def enumerable(enumerable, executor = nil)
|
136
|
+
make(nil, executor) do |tracker|
|
137
|
+
begin
|
138
|
+
enumerable.each do |val|
|
139
|
+
break unless tracker.subscribed?
|
140
|
+
tracker.on_value(val)
|
141
|
+
end
|
142
|
+
|
143
|
+
tracker.on_close if tracker.subscribed?
|
144
|
+
rescue => error
|
145
|
+
tracker.on_error(error) if tracker.subscribed?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def combine_with(function, *trackables)
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def combine_create(type, *trackables, &block)
|
156
|
+
make do |subscriber|
|
157
|
+
main = type.new(block, subscriber)
|
158
|
+
trackables.each do |trackable|
|
159
|
+
trackable.do_track main.subscription!
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize(behaviour = NO_ACTION, executor = nil, &block)
|
166
|
+
@behaviour = block_given? ? block : behaviour
|
167
|
+
@executor = executor
|
168
|
+
end
|
169
|
+
|
170
|
+
def on(trackers = {})
|
171
|
+
unless (trackers.keys - TOPICS).empty?
|
172
|
+
raise "This Trackable supports only #{TOPICS}, " \
|
173
|
+
"but #{trackers.keys} were passed."
|
174
|
+
end
|
175
|
+
|
176
|
+
track(Tracker.new(trackers))
|
177
|
+
end
|
178
|
+
|
179
|
+
def off(notification_tracker)
|
180
|
+
# Clean-up logic
|
181
|
+
end
|
182
|
+
|
183
|
+
def track(notification_tracker)
|
184
|
+
subscription =
|
185
|
+
Subscriptions::TrackerSubscription.new(notification_tracker, self)
|
186
|
+
|
187
|
+
do_track(subscription)
|
188
|
+
|
189
|
+
Subscriptions::SubscriptionWrapper.new(subscription)
|
190
|
+
end
|
191
|
+
|
192
|
+
def lift(operation = nil, &block)
|
193
|
+
operation = block_given? ? block : operation
|
194
|
+
Trackable.new(nil, @executor) do |tracker_subscription|
|
195
|
+
begin
|
196
|
+
lift_behaviour(operation.call(tracker_subscription))
|
197
|
+
rescue Exception => e
|
198
|
+
tracker_subscription.on_error(e)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def flat_map(transform = nil, &block)
|
204
|
+
lift(Operations::FlatMap.new(block_given? ? block : transform))
|
205
|
+
end
|
206
|
+
|
207
|
+
def map(mapping = nil, error: nil, close: nil, &block)
|
208
|
+
lift(Operations::Map.new(
|
209
|
+
block_given? ? block : mapping, error: error, close: close
|
210
|
+
))
|
211
|
+
end
|
212
|
+
|
213
|
+
def select(filter = nil, &block)
|
214
|
+
lift(Operations::Select.new(block_given? ? block : filter))
|
215
|
+
end
|
216
|
+
|
217
|
+
def inject(initial = NO_VALUE, injector = nil, &block)
|
218
|
+
lift(Operations::Inject.new(block_given? ? block : injector, initial))
|
219
|
+
end
|
220
|
+
|
221
|
+
def diff(initial = NO_VALUE, fn = Operations::Diff::DEFAULT_FN, &block)
|
222
|
+
lift(Operations::Diff.new(block_given? ? block : fn, initial))
|
223
|
+
end
|
224
|
+
|
225
|
+
def drop(how_many_to_drop)
|
226
|
+
lift(Operations::Drop.new(how_many_to_drop))
|
227
|
+
end
|
228
|
+
|
229
|
+
def drop_errors
|
230
|
+
lift(Operations::DropErrors.new)
|
231
|
+
end
|
232
|
+
|
233
|
+
def take(how_many_to_take)
|
234
|
+
lift(Operations::Take.new(how_many_to_take))
|
235
|
+
end
|
236
|
+
|
237
|
+
def uniq
|
238
|
+
lift(Operations::Uniq.new)
|
239
|
+
end
|
240
|
+
|
241
|
+
def flatten
|
242
|
+
lift(Operations::Flatten.new)
|
243
|
+
end
|
244
|
+
|
245
|
+
def first
|
246
|
+
take(1)
|
247
|
+
end
|
248
|
+
|
249
|
+
def [](x)
|
250
|
+
lift(Operations::Drop.new(x, 1))
|
251
|
+
end
|
252
|
+
|
253
|
+
def last
|
254
|
+
lift(Operations::Last.new)
|
255
|
+
end
|
256
|
+
|
257
|
+
def prepend(enumerable)
|
258
|
+
lift(Operations::Prepend.new(enumerable))
|
259
|
+
end
|
260
|
+
|
261
|
+
def concat(trackable)
|
262
|
+
lift(Operations::Concat.new(trackable))
|
263
|
+
end
|
264
|
+
|
265
|
+
def merge(trackable, delay_error: false)
|
266
|
+
lift(Operations::Merge.new(trackable, delay_error: delay_error))
|
267
|
+
end
|
268
|
+
|
269
|
+
def buffer(count: nil, delay: nil)
|
270
|
+
lift(Operations::Buffer.new(count: count, delay: delay))
|
271
|
+
end
|
272
|
+
|
273
|
+
def delay(delay)
|
274
|
+
buffer(delay: delay)
|
275
|
+
end
|
276
|
+
|
277
|
+
def throttle(delay)
|
278
|
+
lift(Operations::Throttle.new(delay))
|
279
|
+
end
|
280
|
+
|
281
|
+
def track_on(executor)
|
282
|
+
lift(Operations::TrackOn.new(executor))
|
283
|
+
end
|
284
|
+
|
285
|
+
def execute_on(executor)
|
286
|
+
Trackable.new(@behaviour, executor)
|
287
|
+
end
|
288
|
+
|
289
|
+
def await(subscription, timeout = nil)
|
290
|
+
latch = Concurrent::CountDownLatch.new(1)
|
291
|
+
subscription.add(Subscriptions.on_close_and_error { latch.count_down })
|
292
|
+
latch.wait(timeout)
|
293
|
+
end
|
294
|
+
|
295
|
+
alias_method :skip, :drop
|
296
|
+
alias_method :skip_errors, :drop_errors
|
297
|
+
|
298
|
+
def do_track(subscription)
|
299
|
+
if @executor
|
300
|
+
@executor.post(subscription, &@behaviour)
|
301
|
+
else
|
302
|
+
@behaviour.call(subscription)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
def lift_behaviour(lifted_tracker_subscription)
|
309
|
+
begin
|
310
|
+
lifted_tracker_subscription.on_open
|
311
|
+
@behaviour.call(lifted_tracker_subscription)
|
312
|
+
rescue Exception => e
|
313
|
+
lifted_tracker_subscription.on_error(e)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'reacto/constants'
|
2
|
+
|
3
|
+
module Reacto
|
4
|
+
class Tracker
|
5
|
+
def initialize(
|
6
|
+
open: NO_ACTION,
|
7
|
+
value: NO_ACTION,
|
8
|
+
error: DEFAULT_ON_ERROR,
|
9
|
+
close: NO_ACTION
|
10
|
+
)
|
11
|
+
@open = open
|
12
|
+
@value = value
|
13
|
+
@error = error
|
14
|
+
@close = close
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_open
|
18
|
+
@open.call
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_value(v)
|
22
|
+
@value.call(v)
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_error(error)
|
26
|
+
@error.call(error)
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_close
|
30
|
+
@close.call
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/reacto.rb
ADDED
data/reacto.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
current_dir = File.dirname(File.realpath(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(current_dir, 'lib'))
|
5
|
+
|
6
|
+
require 'reacto/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'reacto'
|
10
|
+
spec.version = Reacto::VERSION
|
11
|
+
spec.authors = ['Nickolay Tzvetinov - Meddle']
|
12
|
+
spec.email = ['n.tzvetinov@gmail.com']
|
13
|
+
spec.description = 'Concurrent Reactive Programming for Ruby'
|
14
|
+
spec.summary = 'Concurrent Reactive Programming for Ruby'
|
15
|
+
spec.homepage = 'https://github.com/meddle0x53/reacto'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.3.0'
|
24
|
+
|
25
|
+
spec.add_dependency 'concurrent-ruby', '~> 1.0.0'
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
context Reacto::Trackable do
|
4
|
+
|
5
|
+
context '.never' do
|
6
|
+
it 'creates a new trackable, which won\'t send any notifications' do
|
7
|
+
attach_test_trackers described_class.never
|
8
|
+
|
9
|
+
expect(test_data).to be_empty
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context '.error' do
|
14
|
+
it 'creates a new trackable emitting only the error passed' do
|
15
|
+
err = StandardError.new('Errrr')
|
16
|
+
attach_test_trackers described_class.error(err)
|
17
|
+
|
18
|
+
expect(test_data).to be == [err]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context '.make' do
|
23
|
+
it 'creates a new trackable with custom behaviour passed as lambda' do
|
24
|
+
behaviour = lambda do |tracker|
|
25
|
+
(1..10).each do |v|
|
26
|
+
tracker.on_value(v)
|
27
|
+
end
|
28
|
+
|
29
|
+
tracker.on_close
|
30
|
+
end
|
31
|
+
|
32
|
+
attach_test_trackers described_class.make(behaviour)
|
33
|
+
|
34
|
+
expect(test_data.size).to be(11)
|
35
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '|']
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'creates a new trackable with custom behaviour passed as block' do
|
39
|
+
trackable = described_class.make do |tracker|
|
40
|
+
(1..10).each do |v|
|
41
|
+
tracker.on_value(v)
|
42
|
+
end
|
43
|
+
|
44
|
+
tracker.on_close
|
45
|
+
end
|
46
|
+
attach_test_trackers trackable
|
47
|
+
|
48
|
+
expect(test_data.size).to be(11)
|
49
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, '|']
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does not emit anything once it is closed' do
|
53
|
+
trackable = described_class.make do |tracker|
|
54
|
+
(1..5).each do |v|
|
55
|
+
tracker.on_value(v)
|
56
|
+
end
|
57
|
+
|
58
|
+
tracker.on_close
|
59
|
+
|
60
|
+
(1..5).each do |v|
|
61
|
+
tracker.on_value(v)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
attach_test_trackers trackable
|
65
|
+
|
66
|
+
expect(test_data.size).to be(6)
|
67
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, '|']
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'does not emit anything once it has an error' do
|
71
|
+
trackable = described_class.make do |tracker|
|
72
|
+
(1..5).each do |v|
|
73
|
+
tracker.on_value(v)
|
74
|
+
end
|
75
|
+
|
76
|
+
tracker.on_error('error')
|
77
|
+
|
78
|
+
(1..5).each do |v|
|
79
|
+
tracker.on_value(v)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
attach_test_trackers trackable
|
83
|
+
|
84
|
+
expect(test_data.size).to be(6)
|
85
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, 'error']
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'emits the same behaviour for every subscribe' do
|
89
|
+
trackable = described_class.make do |tracker|
|
90
|
+
(1..5).each do |v|
|
91
|
+
tracker.on_value(v)
|
92
|
+
end
|
93
|
+
|
94
|
+
tracker.on_close
|
95
|
+
end
|
96
|
+
attach_test_trackers trackable
|
97
|
+
attach_test_trackers trackable
|
98
|
+
|
99
|
+
expect(test_data.size).to be(12)
|
100
|
+
expect(test_data).to be == [1, 2, 3, 4, 5, '|', 1, 2, 3, 4, 5, '|']
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context '.later' do
|
105
|
+
it 'emits the passed value after the passed time runs out and then emits ' \
|
106
|
+
'a close notification' do
|
107
|
+
trackable = described_class.later(0.2, 5)
|
108
|
+
subscription = attach_test_trackers(trackable)
|
109
|
+
|
110
|
+
expect(test_data).to be_empty
|
111
|
+
trackable.await(subscription)
|
112
|
+
expect(test_data).to be == [5, '|']
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'it can use a specific executor' do
|
116
|
+
trackable = described_class.later(
|
117
|
+
0.2, 5, executor: Reacto::Executors.immediate
|
118
|
+
)
|
119
|
+
subscription = attach_test_trackers(trackable)
|
120
|
+
expect(test_data).to be == [5, '|']
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context '.interval' do
|
125
|
+
it 'emits an infinite sequence of number on every n seconds by default' do
|
126
|
+
trackable = described_class.interval(0.1)
|
127
|
+
subscription = attach_test_trackers(trackable)
|
128
|
+
sleep 1
|
129
|
+
subscription.unsubscribe
|
130
|
+
|
131
|
+
expect(test_data).to be == (0..8).to_a
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'can use any enumerator to produce the sequence to emit' do
|
135
|
+
trackable = described_class.interval(0.1, ('a'..'z').each)
|
136
|
+
subscription = attach_test_trackers(trackable)
|
137
|
+
sleep 1
|
138
|
+
subscription.unsubscribe
|
139
|
+
|
140
|
+
expect(test_data).to be == ('a'..'i').to_a
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'can use the immediate executor to block the current thread while ' \
|
144
|
+
'emitting' do
|
145
|
+
trackable = described_class.interval(
|
146
|
+
0.1, (1..5).each, executor: Reacto::Executors.immediate
|
147
|
+
)
|
148
|
+
subscription = attach_test_trackers(trackable)
|
149
|
+
|
150
|
+
expect(test_data).to be == (1..5).to_a + ['|']
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context '.value' do
|
155
|
+
it 'emits only the passed value and then closes' do
|
156
|
+
trackable = described_class.value(5)
|
157
|
+
subscription = attach_test_trackers(trackable)
|
158
|
+
|
159
|
+
expect(test_data).to be == [5, '|']
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context '.enumerable' do
|
164
|
+
it 'emits the whole enumerable one-by-one and then closes' do
|
165
|
+
trackable = described_class.enumerable([1, 3, 5, 6, 7, 8])
|
166
|
+
subscription = attach_test_trackers(trackable)
|
167
|
+
|
168
|
+
expect(test_data).to be == [1, 3, 5, 6, 7, 8, '|']
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'on error it emits all til the error and the error' do
|
172
|
+
class PositiveArray
|
173
|
+
include Enumerable
|
174
|
+
|
175
|
+
def error
|
176
|
+
@error ||= StandardError.new('Bad.')
|
177
|
+
end
|
178
|
+
|
179
|
+
def initialize(*members)
|
180
|
+
@members = members
|
181
|
+
end
|
182
|
+
|
183
|
+
def each(&block)
|
184
|
+
@members.each do |member|
|
185
|
+
raise error if member < 0
|
186
|
+
block.call(member)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
enumerable = PositiveArray.new(2, 3, -4, 3, 6)
|
192
|
+
trackable = described_class.enumerable(enumerable)
|
193
|
+
subscription = attach_test_trackers(trackable)
|
194
|
+
|
195
|
+
expect(test_data).to be == [2, 3, enumerable.error]
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context '.combine' do
|
201
|
+
it 'combines the notifications of Trackables with different number of ' \
|
202
|
+
'notifications using the passed combinator' do
|
203
|
+
trackable1 = described_class.interval(0.3).take(4)
|
204
|
+
trackable2 = described_class.interval(0.7, ('a'..'b').each)
|
205
|
+
trackable3 = described_class.interval(0.5, ('A'..'C').each)
|
206
|
+
|
207
|
+
trackable = described_class.combine(
|
208
|
+
trackable1, trackable2, trackable3
|
209
|
+
) do |v1, v2, v3|
|
210
|
+
"#{v1} : #{v2} : #{v3}"
|
211
|
+
end
|
212
|
+
|
213
|
+
subscription = attach_test_trackers(trackable)
|
214
|
+
trackable.await(subscription)
|
215
|
+
|
216
|
+
expect(test_data).to be == [
|
217
|
+
'1 : a : A', '2 : a : A', '2 : a : B', '3 : a : B',
|
218
|
+
'3 : b : B', '3 : b : C', '|'
|
219
|
+
]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context '.combine_last' do
|
224
|
+
it 'combines the notifications of Trackables based on their ' \
|
225
|
+
'sequence number - the first notification of the sources, then ' \
|
226
|
+
'the next ones and in the end closes if any of the sources closes' do
|
227
|
+
trackable1 = described_class.interval(0.3).take(4)
|
228
|
+
trackable2 = described_class.interval(0.7, ('a'..'b').each)
|
229
|
+
trackable3 = described_class.interval(0.5, ('A'..'C').each)
|
230
|
+
|
231
|
+
trackable = described_class.combine_last(
|
232
|
+
trackable1, trackable2, trackable3
|
233
|
+
) do |v1, v2, v3|
|
234
|
+
"#{v1} : #{v2} : #{v3}"
|
235
|
+
end
|
236
|
+
|
237
|
+
subscription = attach_test_trackers(trackable)
|
238
|
+
trackable.await(subscription)
|
239
|
+
|
240
|
+
expect(test_data).to be == [
|
241
|
+
'1 : a : A', '3 : b : B', '|'
|
242
|
+
]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|