launchpad_mk2 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.
@@ -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