launchpad_mk2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,446 @@
1
+ require 'helper'
2
+
3
+ describe Launchpad::Device do
4
+
5
+ CONTROL_BUTTONS = {
6
+ :up => 0x68,
7
+ :down => 0x69,
8
+ :left => 0x6A,
9
+ :right => 0x6B,
10
+ :session => 0x6C,
11
+ :user1 => 0x6D,
12
+ :user2 => 0x6E,
13
+ :mixer => 0x6F
14
+ }
15
+
16
+ SCENE_BUTTONS = {
17
+ :scene1 => 0x59,
18
+ :scene2 => 0x4F,
19
+ :scene3 => 0x45,
20
+ :scene4 => 0x3B,
21
+ :scene5 => 0x31,
22
+ :scene6 => 0x27,
23
+ :scene7 => 0x1D,
24
+ :scene8 => 0x13
25
+ }
26
+
27
+ STATES = {
28
+ :down => 127,
29
+ :up => 0
30
+ }
31
+
32
+ def expects_output(device, *args)
33
+ args = [args] unless args.first.is_a?(Array)
34
+ messages = args.collect {|data| {:message => data, :timestamp => 0}}
35
+ device.instance_variable_get('@output').expects(:write).with(messages)
36
+ end
37
+
38
+ def expects_sysex_message(device, message)
39
+ device.instance_variable_get('@output').expects(:write_sysex).with(Launchpad::Device::SYSEX_HEADER + message + Launchpad::Device::SYSEX_FOOTER)
40
+ end
41
+
42
+ def stub_input(device, *args)
43
+ device.instance_variable_get('@input').stubs(:read).returns(args)
44
+ end
45
+
46
+ describe '#initialize' do
47
+
48
+ it 'tries to initialize both input and output when not specified' do
49
+ Portmidi.expects(:input_devices).returns(mock_devices)
50
+ Portmidi.expects(:output_devices).returns(mock_devices)
51
+ d = Launchpad::Device.new
52
+ refute_nil d.instance_variable_get('@input')
53
+ refute_nil d.instance_variable_get('@output')
54
+ end
55
+
56
+ it 'does not try to initialize input when set to false' do
57
+ Portmidi.expects(:input_devices).never
58
+ d = Launchpad::Device.new(:input => false)
59
+ assert_nil d.instance_variable_get('@input')
60
+ refute_nil d.instance_variable_get('@output')
61
+ end
62
+
63
+ it 'does not try to initialize output when set to false' do
64
+ Portmidi.expects(:output_devices).never
65
+ d = Launchpad::Device.new(:output => false)
66
+ refute_nil d.instance_variable_get('@input')
67
+ assert_nil d.instance_variable_get('@output')
68
+ end
69
+
70
+ it 'does not try to initialize any of both when set to false' do
71
+ Portmidi.expects(:input_devices).never
72
+ Portmidi.expects(:output_devices).never
73
+ d = Launchpad::Device.new(:input => false, :output => false)
74
+ assert_nil d.instance_variable_get('@input')
75
+ assert_nil d.instance_variable_get('@output')
76
+ end
77
+
78
+ it 'initializes the correct input output devices when specified by name' do
79
+ Portmidi.stubs(:input_devices).returns(mock_devices(:id => 4, :name => 'Launchpad Name'))
80
+ Portmidi.stubs(:output_devices).returns(mock_devices(:id => 5, :name => 'Launchpad Name'))
81
+ d = Launchpad::Device.new(:device_name => 'Launchpad Name')
82
+ assert_equal Portmidi::Input, (input = d.instance_variable_get('@input')).class
83
+ assert_equal 4, input.device_id
84
+ assert_equal Portmidi::Output, (output = d.instance_variable_get('@output')).class
85
+ assert_equal 5, output.device_id
86
+ end
87
+
88
+ it 'initializes the correct input output devices when specified by id' do
89
+ Portmidi.stubs(:input_devices).returns(mock_devices(:id => 4))
90
+ Portmidi.stubs(:output_devices).returns(mock_devices(:id => 5))
91
+ d = Launchpad::Device.new(:input_device_id => 4, :output_device_id => 5, :device_name => 'nonexistant')
92
+ assert_equal Portmidi::Input, (input = d.instance_variable_get('@input')).class
93
+ assert_equal 4, input.device_id
94
+ assert_equal Portmidi::Output, (output = d.instance_variable_get('@output')).class
95
+ assert_equal 5, output.device_id
96
+ end
97
+
98
+ it 'raises NoSuchDeviceError when requested input device does not exist' do
99
+ assert_raises Launchpad::NoSuchDeviceError do
100
+ Portmidi.stubs(:input_devices).returns(mock_devices(:name => 'Launchpad Input'))
101
+ Launchpad::Device.new
102
+ end
103
+ end
104
+
105
+ it 'raises NoSuchDeviceError when requested output device does not exist' do
106
+ assert_raises Launchpad::NoSuchDeviceError do
107
+ Portmidi.stubs(:output_devices).returns(mock_devices(:name => 'Launchpad Output'))
108
+ Launchpad::Device.new
109
+ end
110
+ end
111
+
112
+ it 'raises DeviceBusyError when requested input device is busy' do
113
+ assert_raises Launchpad::DeviceBusyError do
114
+ Portmidi::Input.stubs(:new).raises(RuntimeError)
115
+ Launchpad::Device.new
116
+ end
117
+ end
118
+
119
+ it 'raises DeviceBusyError when requested output device is busy' do
120
+ assert_raises Launchpad::DeviceBusyError do
121
+ Portmidi::Output.stubs(:new).raises(RuntimeError)
122
+ Launchpad::Device.new
123
+ end
124
+ end
125
+
126
+ it 'stores the logger given' do
127
+ logger = Logger.new(nil)
128
+ device = Launchpad::Device.new(:logger => logger)
129
+ assert_same logger, device.logger
130
+ end
131
+
132
+ end
133
+
134
+ describe '#close' do
135
+
136
+ it 'does not fail when neither input nor output are there' do
137
+ Launchpad::Device.new(:input => false, :output => false).close
138
+ end
139
+
140
+ describe 'with input and output devices' do
141
+
142
+ before do
143
+ Portmidi::Input.stubs(:new).returns(@input = mock('input'))
144
+ Portmidi::Output.stubs(:new).returns(@output = mock('output'))
145
+ @device = Launchpad::Device.new
146
+ end
147
+
148
+ it 'closes input/output and raise NoInputAllowedError/NoOutputAllowedError on subsequent read/write accesses' do
149
+ @input.expects(:close)
150
+ @output.expects(:close)
151
+ @device.close
152
+ assert_raises Launchpad::NoInputAllowedError do
153
+ @device.read_pending_actions
154
+ end
155
+ assert_raises Launchpad::NoOutputAllowedError do
156
+ @device.change(:session)
157
+ end
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
164
+ describe '#closed?' do
165
+
166
+ it 'returns true when neither input nor output are there' do
167
+ assert Launchpad::Device.new(:input => false, :output => false).closed?
168
+ end
169
+
170
+ it 'returns false when initialized with input' do
171
+ assert !Launchpad::Device.new(:input => true, :output => false).closed?
172
+ end
173
+
174
+ it 'returns false when initialized with output' do
175
+ assert !Launchpad::Device.new(:input => false, :output => true).closed?
176
+ end
177
+
178
+ it 'returns false when initialized with both but true after calling close' do
179
+ d = Launchpad::Device.new
180
+ assert !d.closed?
181
+ d.close
182
+ assert d.closed?
183
+ end
184
+
185
+ end
186
+
187
+ describe 'top level API initialized with output' do
188
+ before do
189
+ @device = Launchpad::Device.new(:input => false)
190
+ end
191
+
192
+ describe '#pulse1' do
193
+ [[1, 4, 24], [0, 6, 27]].each do |message|
194
+ it "sends 40, 0, #{(message[1] + 1) * 10 + (message[0] + 1)}, #{message[2]} when given #{message}" do
195
+ expects_sysex_message(@device, [40, 0, (message[1] + 1) * 10 + (message[0] + 1), message[2]])
196
+ @device.pulse1(message[0], message[1], message[2])
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#pulsen' do
202
+ it "sends one message for each set of coordinates received" do
203
+ [[1, 4], [0, 6]].each do |coords|
204
+ expects_sysex_message(@device, [40, 0, (coords[1] + 1) * 10 + (coords[0] + 1), 24])
205
+ end
206
+ @device.pulsen([[1, 4], [0, 6]], 24)
207
+ end
208
+ end
209
+
210
+ describe '#flash1' do
211
+ [[1, 4, 24], [0, 6, 27]].each do |message|
212
+ it "sends 35, 0, #{(message[1] + 1) * 10 + (message[0] + 1)}, #{message[2]} when given #{message}" do
213
+ expects_sysex_message(@device, [35, 0, (message[1] + 1) * 10 + (message[0] + 1), message[2]])
214
+ @device.flash1(message[0], message[1], message[2])
215
+ end
216
+ end
217
+ end
218
+
219
+ describe '#flashn' do
220
+ it "sends one message for each set of coordinates received" do
221
+ [[1, 4], [0, 6]].each do |coords|
222
+ expects_sysex_message(@device, [35, 0, (coords[1] + 1) * 10 + (coords[0] + 1), 24, 0])
223
+ end
224
+ @device.flashn([[1, 4], [0, 6]], 24)
225
+ end
226
+ end
227
+
228
+ describe '#light_all' do
229
+ [24, 27].each do |message|
230
+ it "sends 14, #{message[0]} when given #{message}" do
231
+ expects_sysex_message(@device, [14, message[0]])
232
+ @device.light_all(message[0])
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#reset_all' do
238
+ it "sends 14, 0" do
239
+ expects_sysex_message(@device, [14, 0])
240
+ @device.reset_all()
241
+ end
242
+ end
243
+
244
+ describe '#light1_row' do
245
+ [[1, 24], [0, 27]].each do |message|
246
+ it "sends 13, #{message[0]}, #{message[1]} when given #{message}" do
247
+ expects_sysex_message(@device, [13, message[0], message[1]])
248
+ @device.light1_row(message[0], message[1])
249
+ end
250
+ end
251
+ end
252
+
253
+ describe '#light1_column' do
254
+ [[1, 24], [0, 27]].each do |message|
255
+ it "sends 12, #{message[0]}, #{message[1]} when given #{message}" do
256
+ expects_sysex_message(@device, [12, message[0], message[1]])
257
+ @device.light1_column(message[0], message[1])
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ describe '#change' do
264
+
265
+ it 'raises NoOutputAllowedError when not initialized with output' do
266
+ assert_raises Launchpad::NoOutputAllowedError do
267
+ Launchpad::Device.new(:output => false).change(:up)
268
+ end
269
+ end
270
+
271
+ describe 'initialized with output' do
272
+
273
+ before do
274
+ @device = Launchpad::Device.new(:input => false)
275
+ end
276
+
277
+ it 'returns nil' do
278
+ assert_nil @device.change(:up)
279
+ end
280
+
281
+ describe 'control buttons' do
282
+ CONTROL_BUTTONS.each do |type, value|
283
+ it "sends 0xB0, #{value}, 0 when given #{type}" do
284
+ expects_output(@device, 0xB0, value, 0)
285
+ @device.change(type)
286
+ end
287
+ end
288
+ end
289
+
290
+ describe 'scene buttons' do
291
+ SCENE_BUTTONS.each do |type, value|
292
+ it "sends 0x90, #{value}, 0 when given #{type}" do
293
+ expects_output(@device, 0x90, value, 0)
294
+ @device.change(type)
295
+ end
296
+ end
297
+ end
298
+
299
+ describe 'grid buttons' do
300
+ 8.times do |x|
301
+ 8.times do |y|
302
+ it "sends 0x90, #{10 * (y + 1) + (x + 1)}, 0 when given :grid, :x => #{x}, :y => #{y}" do
303
+ expects_output(@device, 0x90, (10 * (y + 1)) + (x + 1), 0)
304
+ @device.change(:grid, :x => x, :y => y)
305
+ end
306
+ end
307
+ end
308
+
309
+ it 'raises NoValidGridCoordinatesError if x is not specified' do
310
+ assert_raises Launchpad::NoValidGridCoordinatesError do
311
+ @device.change(:grid, :y => 1)
312
+ end
313
+ end
314
+
315
+ it 'raises NoValidGridCoordinatesError if x is below 0' do
316
+ assert_raises Launchpad::NoValidGridCoordinatesError do
317
+ @device.change(:grid, :x => -1, :y => 1)
318
+ end
319
+ end
320
+
321
+ it 'raises NoValidGridCoordinatesError if x is above 7' do
322
+ assert_raises Launchpad::NoValidGridCoordinatesError do
323
+ @device.change(:grid, :x => 8, :y => 1)
324
+ end
325
+ end
326
+
327
+ it 'raises NoValidGridCoordinatesError if y is not specified' do
328
+ assert_raises Launchpad::NoValidGridCoordinatesError do
329
+ @device.change(:grid, :x => 1)
330
+ end
331
+ end
332
+
333
+ it 'raises NoValidGridCoordinatesError if y is below 0' do
334
+ assert_raises Launchpad::NoValidGridCoordinatesError do
335
+ @device.change(:grid, :x => 1, :y => -1)
336
+ end
337
+ end
338
+
339
+ it 'raises NoValidGridCoordinatesError if y is above 7' do
340
+ assert_raises Launchpad::NoValidGridCoordinatesError do
341
+ @device.change(:grid, :x => 1, :y => 8)
342
+ end
343
+ end
344
+
345
+ end
346
+
347
+ describe 'colors' do
348
+ (0..127).each do |color_key|
349
+ it "sends 0x90, 0, #{color_key} when given :color => #{color_key}" do
350
+ expects_output(@device, 0x90, 11, color_key)
351
+ @device.change(:grid, :x => 0, :y => 0, :color => color_key)
352
+ end
353
+ end
354
+
355
+ it 'raises NoValidColorError if color is below 0' do
356
+ assert_raises Launchpad::NoValidColorError do
357
+ @device.change(:grid, :x => 0, :y => 0, :color => -1)
358
+ end
359
+ end
360
+
361
+ it 'raises NoValidColorError if color is above 127' do
362
+ assert_raises Launchpad::NoValidColorError do
363
+ @device.change(:grid, :x => 0, :y => 0, :color => 128)
364
+ end
365
+ end
366
+
367
+ it 'raises NoValidColorError if color is an unknown symbol' do
368
+ assert_raises Launchpad::NoValidColorError do
369
+ @device.change(:grid, :x => 0, :y => 0, :color => :unknown)
370
+ end
371
+ end
372
+
373
+ end
374
+
375
+ describe 'mode' do
376
+
377
+ it 'sends 0 when nothing given' do
378
+ expects_output(@device, 0x90, 11, 0)
379
+ @device.change(:grid, :x => 0, :y => 0)
380
+ end
381
+
382
+ end
383
+
384
+ end
385
+
386
+ end
387
+
388
+ describe '#read_pending_actions' do
389
+
390
+ it 'raises NoInputAllowedError when not initialized with input' do
391
+ assert_raises Launchpad::NoInputAllowedError do
392
+ Launchpad::Device.new(:input => false).read_pending_actions
393
+ end
394
+ end
395
+
396
+ describe 'initialized with input' do
397
+
398
+ before do
399
+ @device = Launchpad::Device.new(:output => false)
400
+ end
401
+
402
+ describe 'control buttons' do
403
+ CONTROL_BUTTONS.each do |type, value|
404
+ STATES.each do |state, velocity|
405
+ it "builds proper action for control button #{type}, #{state}" do
406
+ stub_input(@device, {:timestamp => 0, :message => [0xB0, value, velocity]})
407
+ assert_equal [{:timestamp => 0, :state => state, :type => type}], @device.read_pending_actions
408
+ end
409
+ end
410
+ end
411
+ end
412
+
413
+ describe 'scene buttons' do
414
+ SCENE_BUTTONS.each do |type, value|
415
+ STATES.each do |state, velocity|
416
+ it "builds proper action for scene button #{type}, #{state}" do
417
+ stub_input(@device, {:timestamp => 0, :message => [0x90, value, velocity]})
418
+ assert_equal [{:timestamp => 0, :state => state, :type => type}], @device.read_pending_actions
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ describe '#grid buttons' do
425
+ 8.times do |x|
426
+ 8.times do |y|
427
+ STATES.each do |state, velocity|
428
+ it "builds proper action for grid button #{x},#{y}, #{state}" do
429
+ stub_input(@device, {:timestamp => 0, :message => [0x90, 10 * (y + 1) + (x + 1), velocity]})
430
+ assert_equal [{:timestamp => 0, :state => state, :type => :grid, :x => x, :y => y}], @device.read_pending_actions
431
+ end
432
+ end
433
+ end
434
+ end
435
+ end
436
+
437
+ it 'builds proper actions for multiple pending actions' do
438
+ stub_input(@device, {:timestamp => 1, :message => [0x90, 11, 127]}, {:timestamp => 2, :message => [0xB0, 0x68, 0]})
439
+ assert_equal [{:timestamp => 1, :state => :down, :type => :grid, :x => 0, :y => 0}, {:timestamp => 2, :state => :up, :type => :up}], @device.read_pending_actions
440
+ end
441
+
442
+ end
443
+
444
+ end
445
+
446
+ end
@@ -0,0 +1,460 @@
1
+ require 'helper'
2
+ require 'timeout'
3
+
4
+ class BreakError < StandardError; end
5
+
6
+ describe Launchpad::Interaction do
7
+
8
+ before do
9
+ @interaction = nil
10
+ @reader = nil
11
+ @grid_down = nil
12
+ @mixer_down = nil
13
+ @mixer_up = nil
14
+ @mixer = nil
15
+ @all_up = nil
16
+ end
17
+
18
+
19
+ # returns true/false whether the operation ended or the timeout was hit
20
+ def timeout(timeout = 0.02, &block)
21
+ Timeout.timeout(timeout, &block)
22
+ true
23
+ rescue Timeout::Error
24
+ false
25
+ end
26
+
27
+ def press(interaction, type, opts = nil)
28
+ interaction.respond_to(type, :down, opts)
29
+ interaction.respond_to(type, :up, opts)
30
+ end
31
+
32
+
33
+ def press_all(interaction)
34
+ %w(up down left right session user1 user2 mixer).each do |type|
35
+ press(interaction, type.to_sym)
36
+ end
37
+ 8.times do |y|
38
+ 8.times do |x|
39
+ press(interaction, :grid, :x => x, :y => y)
40
+ end
41
+ press(interaction, :"scene#{y + 1}")
42
+ end
43
+ end
44
+
45
+ describe '#initialize' do
46
+
47
+ it 'creates device if not given' do
48
+ device = Launchpad::Device.new
49
+ Launchpad::Device.expects(:new).
50
+ with(:input => true, :output => true, :logger => nil).
51
+ returns(device)
52
+ interaction = Launchpad::Interaction.new
53
+ assert_same device, interaction.device
54
+ end
55
+
56
+ it 'creates device with given device_name' do
57
+ device = Launchpad::Device.new
58
+ Launchpad::Device.expects(:new).
59
+ with(:device_name => 'device', :input => true, :output => true, :logger => nil).
60
+ returns(device)
61
+ interaction = Launchpad::Interaction.new(:device_name => 'device')
62
+ assert_same device, interaction.device
63
+ end
64
+
65
+ it 'creates device with given input_device_id' do
66
+ device = Launchpad::Device.new
67
+ Launchpad::Device.expects(:new).
68
+ with(:input_device_id => 'in', :input => true, :output => true, :logger => nil).
69
+ returns(device)
70
+ interaction = Launchpad::Interaction.new(:input_device_id => 'in')
71
+ assert_same device, interaction.device
72
+ end
73
+
74
+ it 'creates device with given output_device_id' do
75
+ device = Launchpad::Device.new
76
+ Launchpad::Device.expects(:new).
77
+ with(:output_device_id => 'out', :input => true, :output => true, :logger => nil).
78
+ returns(device)
79
+ interaction = Launchpad::Interaction.new(:output_device_id => 'out')
80
+ assert_same device, interaction.device
81
+ end
82
+
83
+ it 'creates device with given input_device_id/output_device_id' do
84
+ device = Launchpad::Device.new
85
+ Launchpad::Device.expects(:new).
86
+ with(:input_device_id => 'in', :output_device_id => 'out', :input => true, :output => true, :logger => nil).
87
+ returns(device)
88
+ interaction = Launchpad::Interaction.new(:input_device_id => 'in', :output_device_id => 'out')
89
+ assert_same device, interaction.device
90
+ end
91
+
92
+ it 'initializes device if given' do
93
+ device = Launchpad::Device.new
94
+ interaction = Launchpad::Interaction.new(:device => device)
95
+ assert_same device, interaction.device
96
+ end
97
+
98
+ it 'stores the logger given' do
99
+ logger = Logger.new(nil)
100
+ interaction = Launchpad::Interaction.new(:logger => logger)
101
+ assert_same logger, interaction.logger
102
+ assert_same logger, interaction.device.logger
103
+ end
104
+
105
+ it 'doesn\'t activate the interaction' do
106
+ assert !Launchpad::Interaction.new.active
107
+ end
108
+
109
+ end
110
+
111
+ describe '#logger=' do
112
+
113
+ it 'stores the logger and passes it to the device as well' do
114
+ logger = Logger.new(nil)
115
+ interaction = Launchpad::Interaction.new
116
+ interaction.logger = logger
117
+ assert_same logger, interaction.logger
118
+ assert_same logger, interaction.device.logger
119
+ end
120
+
121
+ end
122
+
123
+ describe '#close' do
124
+
125
+ it 'stops the interaction' do
126
+ interaction = Launchpad::Interaction.new
127
+ interaction.expects(:stop)
128
+ interaction.close
129
+ end
130
+
131
+ it 'closes the device' do
132
+ interaction = Launchpad::Interaction.new
133
+ interaction.device.expects(:close)
134
+ interaction.close
135
+ end
136
+
137
+ end
138
+
139
+ describe '#closed?' do
140
+
141
+ it 'returns false on a newly created interaction, but true after closing' do
142
+ interaction = Launchpad::Interaction.new
143
+ assert !interaction.closed?
144
+ interaction.close
145
+ assert interaction.closed?
146
+ end
147
+
148
+ end
149
+
150
+ describe '#start' do
151
+
152
+ before do
153
+ @interaction = Launchpad::Interaction.new
154
+ end
155
+
156
+ after do
157
+ mocha_teardown # so that expectations on Thread.join don't fail in here
158
+ begin
159
+ @interaction.close
160
+ rescue
161
+ # ignore, should be handled in tests, this is just to close all the spawned threads
162
+ end
163
+ end
164
+
165
+ it 'sets active to true in blocking mode' do
166
+ refute @interaction.active
167
+ erg = timeout { @interaction.start }
168
+ refute erg, 'there was no timeout'
169
+ assert @interaction.active
170
+ end
171
+
172
+ it 'sets active to true in detached mode' do
173
+ refute @interaction.active
174
+ @interaction.start(:detached => true)
175
+ assert @interaction.active
176
+ end
177
+
178
+ it 'blocks in blocking mode' do
179
+ erg = timeout { @interaction.start }
180
+ refute erg, 'there was no timeout'
181
+ end
182
+
183
+ it 'returns immediately in detached mode' do
184
+ erg = timeout { @interaction.start(:detached => true) }
185
+ assert erg, 'there was a timeout'
186
+ end
187
+
188
+ it 'raises CommunicationError when Portmidi::DeviceError occurs' do
189
+ @interaction.device.stubs(:read_pending_actions).raises(Portmidi::DeviceError.new(0))
190
+ assert_raises Launchpad::CommunicationError do
191
+ @interaction.start
192
+ end
193
+ end
194
+
195
+ describe 'action handling' do
196
+
197
+ before do
198
+ @interaction.response_to(:mixer, :down) { @mixer_down = true }
199
+ @interaction.response_to(:mixer, :up) do |i,a|
200
+ sleep 0.001 # sleep to make "sure" :mixer :down has been processed
201
+ i.stop
202
+ end
203
+ @interaction.device.expects(:read_pending_actions).
204
+ at_least_once.
205
+ returns([
206
+ {
207
+ :timestamp => 0,
208
+ :state => :down,
209
+ :type => :mixer
210
+ },
211
+ {
212
+ :timestamp => 0,
213
+ :state => :up,
214
+ :type => :mixer
215
+ }
216
+ ])
217
+ end
218
+
219
+ it 'calls respond_to_action with actions from respond_to_action in blocking mode' do
220
+ erg = timeout(0.5) { @interaction.start }
221
+ assert erg, 'the actions weren\'t called'
222
+ assert @mixer_down, 'the mixer button wasn\'t pressed'
223
+ end
224
+
225
+ it 'calls respond_to_action with actions from respond_to_action in detached mode' do
226
+ @interaction.start(:detached => true)
227
+ erg = timeout(0.5) { while @interaction.active; sleep 0.01; end }
228
+ assert erg, 'there was a timeout'
229
+ assert @mixer_down, 'the mixer button wasn\'t pressed'
230
+ end
231
+
232
+ end
233
+
234
+ describe 'latency' do
235
+
236
+ before do
237
+ @device = @interaction.device
238
+ @times = []
239
+ @device.instance_variable_set("@test_interaction_latency_times", @times)
240
+ def @device.read_pending_actions
241
+ @test_interaction_latency_times << Time.now.to_f
242
+ []
243
+ end
244
+ end
245
+
246
+ it 'sleeps with default latency of 0.001s when none given' do
247
+ timeout { @interaction.start }
248
+ assert @times.size > 1
249
+ @times.each_cons(2) do |a,b|
250
+ assert_in_delta 0.001, b - a, 0.01
251
+ end
252
+ end
253
+
254
+ it 'sleeps with given latency' do
255
+ @interaction = Launchpad::Interaction.new(:latency => 0.5, :device => @device)
256
+ timeout(0.55) { @interaction.start }
257
+ assert @times.size > 1
258
+ @times.each_cons(2) do |a,b|
259
+ assert_in_delta 0.5, b - a, 0.01
260
+ end
261
+ end
262
+
263
+ it 'sleeps with absolute value of given negative latency' do
264
+ @interaction = Launchpad::Interaction.new(:latency => -0.1, :device => @device)
265
+ timeout(0.15) { @interaction.start }
266
+ assert @times.size > 1
267
+ @times.each_cons(2) do |a,b|
268
+ assert_in_delta 0.1, b - a, 0.01
269
+ end
270
+ end
271
+
272
+ it 'does not sleep when latency is 0' do
273
+ @interaction = Launchpad::Interaction.new(:latency => 0, :device => @device)
274
+ timeout(0.001) { @interaction.start }
275
+ assert @times.size > 1
276
+ @times.each_cons(2) do |a,b|
277
+ assert_in_delta 0, b - a, 0.1
278
+ end
279
+ end
280
+
281
+ end
282
+
283
+ it 'raises NoInputAllowedError on closed interaction' do
284
+ @interaction.close
285
+ assert_raises Launchpad::NoInputAllowedError do
286
+ @interaction.start
287
+ end
288
+ end
289
+
290
+ end
291
+
292
+ describe '#stop' do
293
+
294
+ before do
295
+ @interaction = Launchpad::Interaction.new
296
+ end
297
+
298
+ it 'sets active to false in blocking mode' do
299
+ erg = timeout { @interaction.start }
300
+ refute erg, 'there was no timeout'
301
+ assert @interaction.active
302
+ @interaction.stop
303
+ assert !@interaction.active
304
+ end
305
+
306
+ it 'sets active to false in detached mode' do
307
+ @interaction.start(:detached => true)
308
+ assert @interaction.active
309
+ @interaction.stop
310
+ assert !@interaction.active
311
+ end
312
+
313
+ it 'is callable anytime' do
314
+ @interaction.stop
315
+ @interaction.start(:detached => true)
316
+ @interaction.stop
317
+ @interaction.stop
318
+ end
319
+
320
+ # this is kinda greybox tested, since I couldn't come up with another way to test tread handling [thomas, 2010-01-24]
321
+ it 'raises pending exceptions in detached mode' do
322
+ t = Thread.new {raise BreakError}
323
+ Thread.expects(:new).returns(t)
324
+ @interaction.start(:detached => true)
325
+ assert_raises BreakError do
326
+ @interaction.stop
327
+ end
328
+ end
329
+
330
+ end
331
+
332
+ describe '#response_to/#no_response_to/#respond_to' do
333
+
334
+ before do
335
+ @interaction = Launchpad::Interaction.new
336
+ end
337
+
338
+ it 'calls all responses that match, and not others' do
339
+ @interaction.response_to(:mixer, :down) {|i, a| @mixer_down = true}
340
+ @interaction.response_to(:all, :down) {|i, a| @all_down = true}
341
+ @interaction.response_to(:all, :up) {|i, a| @all_up = true}
342
+ @interaction.response_to(:grid, :down) {|i, a| @grid_down = true}
343
+ @interaction.respond_to(:mixer, :down)
344
+ assert @mixer_down
345
+ assert @all_down
346
+ assert !@all_up
347
+ assert !@grid_down
348
+ end
349
+
350
+ it 'does not call responses when they are deregistered' do
351
+ @interaction.response_to(:mixer, :down) {|i, a| @mixer_down = true}
352
+ @interaction.response_to(:mixer, :up) {|i, a| @mixer_up = true}
353
+ @interaction.response_to(:all, :both) {|i, a| @all_down = a[:state] == :down}
354
+ @interaction.no_response_to(:mixer, :down)
355
+ @interaction.respond_to(:mixer, :down)
356
+ assert !@mixer_down
357
+ assert !@mixer_up
358
+ assert @all_down
359
+ @interaction.respond_to(:mixer, :up)
360
+ assert !@mixer_down
361
+ assert @mixer_up
362
+ assert !@all_down
363
+ end
364
+
365
+ it 'does not call responses registered for both when removing for one of both states' do
366
+ @interaction.response_to(:mixer, :both) {|i, a| @mixer = true}
367
+ @interaction.no_response_to(:mixer, :down)
368
+ @interaction.respond_to(:mixer, :down)
369
+ assert !@mixer
370
+ @interaction.respond_to(:mixer, :up)
371
+ assert @mixer
372
+ end
373
+
374
+ it 'removes other responses when adding a new exclusive response' do
375
+ @interaction.response_to(:mixer, :both) {|i, a| @mixer = true}
376
+ @interaction.response_to(:mixer, :down, :exclusive => true) {|i, a| @exclusive_mixer = true}
377
+ @interaction.respond_to(:mixer, :down)
378
+ assert !@mixer
379
+ assert @exclusive_mixer
380
+ @interaction.respond_to(:mixer, :up)
381
+ assert @mixer
382
+ assert @exclusive_mixer
383
+ end
384
+
385
+ it 'allows for multiple types' do
386
+ @downs = []
387
+ @interaction.response_to([:up, :down], :down) {|i, a| @downs << a[:type]}
388
+ @interaction.respond_to(:up, :down)
389
+ @interaction.respond_to(:down, :down)
390
+ @interaction.respond_to(:up, :down)
391
+ assert_equal [:up, :down, :up], @downs
392
+ end
393
+
394
+ describe 'allows to bind to specific grid buttons' do
395
+
396
+ before do
397
+ @downs = []
398
+ @action = lambda {|i, a| @downs << [a[:x], a[:y]]}
399
+ end
400
+
401
+ it 'one specific grid button' do
402
+ @interaction.response_to(:grid, :down, :x => 4, :y => 2, &@action)
403
+ press_all @interaction
404
+ assert_equal [[4, 2]], @downs
405
+ end
406
+
407
+ it 'a complete row of grid buttons' do
408
+ @interaction.response_to(:grid, :down, :y => 2, &@action)
409
+ press_all @interaction
410
+ assert_equal [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2], [6, 2], [7, 2]], @downs
411
+ end
412
+
413
+ it 'a complete column of grid buttons' do
414
+ @interaction.response_to(:grid, :down, :x => 3, &@action)
415
+ press_all @interaction
416
+ assert_equal [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7]], @downs
417
+ end
418
+
419
+ it 'a complex range of grid buttons' do
420
+ @interaction.response_to(:grid, :down, :x => [1,[2]], :y => [1, 3..5], &@action)
421
+ press_all @interaction
422
+ assert_equal [[1, 1], [2, 1], [1, 3], [2, 3], [1, 4], [2, 4], [1, 5], [2, 5]], @downs
423
+ end
424
+
425
+ it 'a specific grid buttons, a column, a row, all grid buttons and all buttons' do
426
+ @interaction.response_to(:all, :down) {|i, a| @downs << [a[:x], a[:y], :all]}
427
+ @interaction.response_to(:grid, :down) {|i, a| @downs << [a[:x], a[:y], :grid]}
428
+ @interaction.response_to(:grid, :down, :x => 0) {|i, a| @downs << [a[:x], a[:y], :col]}
429
+ @interaction.response_to(:grid, :down, :y => 0) {|i, a| @downs << [a[:x], a[:y], :row]}
430
+ @interaction.response_to(:grid, :down, :x => 0, :y => 0, &@action)
431
+ press @interaction, :grid, :x => 0, :y => 0
432
+ assert_equal [[0, 0], [0, 0, :col], [0, 0, :row], [0, 0, :grid], [0, 0, :all]], @downs
433
+ end
434
+
435
+ end
436
+
437
+ end
438
+
439
+ describe 'regression tests' do
440
+
441
+ it 'does not raise an exception or write an error to the logger when calling stop within a response in attached mode' do
442
+ log = StringIO.new
443
+ logger = Logger.new(log)
444
+ logger.level = Logger::ERROR
445
+ i = Launchpad::Interaction.new(:logger => logger)
446
+ i.response_to(:mixer, :down) {|current_interaction,a| current_interaction.stop}
447
+ i.device.expects(:read_pending_actions).
448
+ at_least_once.
449
+ returns([{
450
+ :timestamp => 0,
451
+ :state => :down,
452
+ :type => :mixer
453
+ }])
454
+ timeout { i.start }
455
+ assert_equal '', log.string
456
+ end
457
+
458
+ end
459
+
460
+ end