vigilem-evdev 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/ext/Rakefile +31 -0
- data/ext/rake_helper.rb +10 -0
- data/lib/vigilem/_evdev.rb +24 -0
- data/lib/vigilem/evdev.rb +14 -0
- data/lib/vigilem/evdev/at_exit.rb +8 -0
- data/lib/vigilem/evdev/context_filter.rb +92 -0
- data/lib/vigilem/evdev/demultiplexer.rb +26 -0
- data/lib/vigilem/evdev/device.rb +182 -0
- data/lib/vigilem/evdev/device_capabilities.rb +53 -0
- data/lib/vigilem/evdev/dom.rb +8 -0
- data/lib/vigilem/evdev/dom/adapter.rb +87 -0
- data/lib/vigilem/evdev/dom/code_values_tables.rb +172 -0
- data/lib/vigilem/evdev/dom/input_event_converter.rb +329 -0
- data/lib/vigilem/evdev/dom/input_event_utils.rb +48 -0
- data/lib/vigilem/evdev/dom/key_values_tables.rb +248 -0
- data/lib/vigilem/evdev/dom/kp_table.rb +52 -0
- data/lib/vigilem/evdev/focus_context_filter.rb +124 -0
- data/lib/vigilem/evdev/input_system_handler.rb +69 -0
- data/lib/vigilem/evdev/key_map_cache.rb +64 -0
- data/lib/vigilem/evdev/multiplexer.rb +73 -0
- data/lib/vigilem/evdev/system.rb +17 -0
- data/lib/vigilem/evdev/system/input.rb +1053 -0
- data/lib/vigilem/evdev/system/input/event.rb +4 -0
- data/lib/vigilem/evdev/system/input/input_event.rb +33 -0
- data/lib/vigilem/evdev/system/int.rb +9 -0
- data/lib/vigilem/evdev/system/ioctl.rb +143 -0
- data/lib/vigilem/evdev/system/keymap_loaders.rb +89 -0
- data/lib/vigilem/evdev/system/keymap_loaders/dumpkeys_loader.rb +98 -0
- data/lib/vigilem/evdev/system/keymap_loaders/kmap_loader.rb +74 -0
- data/lib/vigilem/evdev/system/posix_types.rb +5 -0
- data/lib/vigilem/evdev/system/time.rb +21 -0
- data/lib/vigilem/evdev/transfer_agent.rb +40 -0
- data/lib/vigilem/evdev/version.rb +5 -0
- data/lib/vigilem/evdev/vty_context_filter.rb +216 -0
- data/spec/after_each_example_group.rb +29 -0
- data/spec/delete_test_cache_after_group.rb +26 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/vigilem/_evdev_spec.rb +11 -0
- data/spec/vigilem/evdev/context_filter_spec.rb +114 -0
- data/spec/vigilem/evdev/demultiplexer_spec.rb +44 -0
- data/spec/vigilem/evdev/device_capabilities_spec.rb +0 -0
- data/spec/vigilem/evdev/device_spec.rb +97 -0
- data/spec/vigilem/evdev/dom/adapter_spec.rb +5 -0
- data/spec/vigilem/evdev/dom/input_event_converter_spec.rb +253 -0
- data/spec/vigilem/evdev/dom/input_event_utils_spec.rb +23 -0
- data/spec/vigilem/evdev/focus_context_filter_spec.rb +112 -0
- data/spec/vigilem/evdev/input_system_handler_spec.rb +146 -0
- data/spec/vigilem/evdev/key_map_cache_spec.rb +65 -0
- data/spec/vigilem/evdev/multiplexer_spec.rb +55 -0
- data/spec/vigilem/evdev/system/input/input_event_spec.rb +33 -0
- data/spec/vigilem/evdev/system/input_spec.rb +274 -0
- data/spec/vigilem/evdev/system/int_spec.rb +30 -0
- data/spec/vigilem/evdev/system/ioctl_spec.rb +206 -0
- data/spec/vigilem/evdev/system/keymap_loaders/dumpkeys_loader_spec.rb +5 -0
- data/spec/vigilem/evdev/system/keymap_loaders/kmap_loader_spec.rb +24 -0
- data/spec/vigilem/evdev/system/keymap_loaders_spec.rb +22 -0
- data/spec/vigilem/evdev/system/posix_types_spec.rb +15 -0
- data/spec/vigilem/evdev/system/time_spec.rb +18 -0
- data/spec/vigilem/evdev/transfer_agent_spec.rb +57 -0
- data/spec/vigilem/evdev/vty_context_filter_spec.rb +282 -0
- metadata +286 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'vigilem/support'
|
2
|
+
|
3
|
+
require 'vigilem/evdev/system/posix_types'
|
4
|
+
|
5
|
+
module Vigilem
|
6
|
+
module Evdev
|
7
|
+
module Time
|
8
|
+
# represents timeval from time.h
|
9
|
+
class Timeval < ::VFFIStruct
|
10
|
+
layout_with_methods :tv_sec, :__kernel_time_t,
|
11
|
+
:tv_usec, :__kernel_suseconds_t
|
12
|
+
|
13
|
+
#
|
14
|
+
# @return [Numeric]
|
15
|
+
def to_f
|
16
|
+
tv_sec.to_f + (tv_usec.to_f/1000000.0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'vigilem/core/transfer_agent'
|
2
|
+
|
3
|
+
require 'vigilem/evdev/demultiplexer'
|
4
|
+
|
5
|
+
require 'vigilem/evdev/multiplexer'
|
6
|
+
|
7
|
+
module Vigilem
|
8
|
+
module Evdev
|
9
|
+
#
|
10
|
+
#
|
11
|
+
class TransferAgent < Core::TransferAgent
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# @todo move away from singleton and look at the one used by x11
|
16
|
+
# @param args [Hash]
|
17
|
+
# @param :inputs [Array]
|
18
|
+
# @param :outputs [Hash{observer_object => {event_opts}]
|
19
|
+
# @see Demultiplexer#add_oberver
|
20
|
+
# @return [TransferAgent]
|
21
|
+
def acquire(args={})
|
22
|
+
if @transfer_agent
|
23
|
+
@transfer_agent.multiplexer.add_inputs(*args[:inputs]) if args[:inputs]
|
24
|
+
@transfer_agent.add_observers(args[:outputs]) if args[:outputs]
|
25
|
+
if ((din = @transfer_agent.demultiplexer.input) || (mout = @transfer_agent.multiplexer.out)).nil? or not mout.equal?(din)
|
26
|
+
@transfer_agent.demultiplexer.input = @transfer_agent.multiplexer.out = (mout || din || [])
|
27
|
+
end
|
28
|
+
@transfer_agent
|
29
|
+
else
|
30
|
+
m = Multiplexer.acquire(args[:inputs])
|
31
|
+
d = Demultiplexer.acquire(args[:outputs])
|
32
|
+
@transfer_agent = new(d, m)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'vigilem/support/core_ext'
|
2
|
+
|
3
|
+
require 'vigilem/support/key_map'
|
4
|
+
|
5
|
+
require 'vigilem/_evdev'
|
6
|
+
|
7
|
+
require 'vigilem/evdev/context_filter'
|
8
|
+
|
9
|
+
module Vigilem
|
10
|
+
module Evdev
|
11
|
+
#
|
12
|
+
# filters events if the VTY differs from the one of this process
|
13
|
+
class VTYContextFilter
|
14
|
+
|
15
|
+
KeyMap = Support::KeyMap
|
16
|
+
|
17
|
+
include ContextFilter
|
18
|
+
# @todo make these class_methods
|
19
|
+
attr_reader :current_console_num, :console_keys,
|
20
|
+
:current_console_keys, :other_console_keys
|
21
|
+
|
22
|
+
#
|
23
|
+
# @param [KeyMap] keymap
|
24
|
+
# @return
|
25
|
+
def initialize(keymap=Evdev.key_map)
|
26
|
+
@key_map = keymap unless keymap.eql? Evdev.key_map
|
27
|
+
|
28
|
+
@current_console_num = `fgconsole`.rstrip.to_i
|
29
|
+
curr_con_keys, oth_con_keys =
|
30
|
+
self.class.console_keys(keymap).partition {|syms,v| v == "Console_#{@current_console_num}" }
|
31
|
+
@current_console_keys, @other_console_keys = KeyMap[curr_con_keys], KeyMap[oth_con_keys]
|
32
|
+
|
33
|
+
@current_console_keys.left_side_aliases(:keycode, :keycodes)
|
34
|
+
@current_console_keys.right_side_aliases(:keysym, :keysyms)
|
35
|
+
|
36
|
+
@other_console_keys.left_side_aliases(:keycode, :keycodes)
|
37
|
+
@other_console_keys.right_side_aliases(:keysym, :keysyms)
|
38
|
+
|
39
|
+
@current_keys = []
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @param [KeyMap] keymap
|
44
|
+
# @return [KeyMap]
|
45
|
+
def self.console_keys(keymap)
|
46
|
+
@console_keys = if keymap.metadata[:loader].class.name =~ /KmapLoader/
|
47
|
+
keymap.select {|k,v| v =~ /console_\d+/i }
|
48
|
+
else
|
49
|
+
console_syms = keymap.info.keysyms.select {|k,v| v =~ /console_\d+/i }
|
50
|
+
tmp = keymap.select {|k,v| v =~ /^#{console_syms.keys.join('|')}$/ }
|
51
|
+
tmp.each {|k,v| tmp[k] = console_syms[v] }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# @return [KeyMap]
|
57
|
+
def key_map
|
58
|
+
@key_map || Evdev.key_map
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# @return [Array]
|
63
|
+
def current_keys
|
64
|
+
if @current_keys and current_keys_changed?
|
65
|
+
max_and_one = KeyMap.mod_weights.values.max + 1
|
66
|
+
@current_keys.sort_by! {|ksym| KeyMap.mod_weights[ksym.downcase] || max_and_one }
|
67
|
+
@current_keys_hash_cache = @current_keys.hash
|
68
|
+
@current_keys
|
69
|
+
else
|
70
|
+
if @current_keys.nil?
|
71
|
+
@current_keys = []
|
72
|
+
@current_keys_hash_cache = @current_keys.hash
|
73
|
+
end
|
74
|
+
@current_keys
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# @param [Array<System::InputEvent>] events
|
80
|
+
# @return [Array]
|
81
|
+
def process(*events)
|
82
|
+
events.select do |event|
|
83
|
+
off_status = off?
|
84
|
+
key_check = if event.type == System::Input::EV_KEY
|
85
|
+
if event.value == 1
|
86
|
+
add_key(keysym_or_keycode(event.code))
|
87
|
+
off_status
|
88
|
+
elsif event.value == 0
|
89
|
+
kork = keysym_or_keycode(event.code)
|
90
|
+
release_status = allow_release?(kork)
|
91
|
+
remove_key(kork)
|
92
|
+
release_status
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if key_check.nil? then off_status else key_check end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# @param key
|
101
|
+
# @return [Array]
|
102
|
+
def add_key(keycode_or_keysym)
|
103
|
+
current_keys << keycode_or_keysym
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# @param [Fixnum] key_num
|
108
|
+
# @return
|
109
|
+
def remove_key(keycode_or_keysym)
|
110
|
+
current_keys.delete(keycode_or_keysym)
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# @todo name
|
115
|
+
# @return [String]
|
116
|
+
def keysym_or_keycode(key_num)
|
117
|
+
sym = keysym(keycode = "keycode#{key_num}")
|
118
|
+
if sym =~ /shift|alt|c(on)?tro?l|capsshift/i
|
119
|
+
sym
|
120
|
+
else
|
121
|
+
keycode
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# @todo move to KeyMapUtils, or something like that
|
126
|
+
# @param [Integer] key_num
|
127
|
+
# @return [String]
|
128
|
+
def keysym(keycode)
|
129
|
+
if key_map.metadata[:loader].class.name =~ /KmapLoader/
|
130
|
+
key_map.keysym(keycode)
|
131
|
+
else
|
132
|
+
key_map.info.keysyms[key_map.char_ref(keycode)]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# @return [TrueClass || FalseClass || NilClass]
|
138
|
+
def on?
|
139
|
+
if changed_vty?
|
140
|
+
self.filter_ommisions = current_keys_downcase
|
141
|
+
on_change(ON)
|
142
|
+
elsif reverted_vty?
|
143
|
+
self.filter_ommisions = current_keys_downcase
|
144
|
+
on_change(OFF)
|
145
|
+
end
|
146
|
+
was_on?
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# @return [TrueClass || FalseClass]
|
151
|
+
def reverted_vty?
|
152
|
+
was_on? and current_console_keys.keysym(current_keys_downcase)
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# @return [TrueClass || FalseClass]
|
157
|
+
def changed_vty?
|
158
|
+
was_off? and other_console_keys.keysym(current_keys_downcase)
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# @return [TrueClass || FalseClass]
|
163
|
+
def off?
|
164
|
+
not on?
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
#
|
169
|
+
# @return [Integer] the hash of current_keys last time it was set
|
170
|
+
def current_keys_hash_cache
|
171
|
+
@current_keys_hash_cache ||= @current_keys.hash
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# @return [TrueClass || FalseClass] if the cached value of
|
176
|
+
# #current_keys.hash changed
|
177
|
+
def current_keys_changed?
|
178
|
+
current_keys_hash_cache != @current_keys.hash
|
179
|
+
end
|
180
|
+
|
181
|
+
# @todo name!
|
182
|
+
# @return [Array]
|
183
|
+
def current_keys_downcase
|
184
|
+
downcase_hash ||= 0
|
185
|
+
if @current_keys_downcase.nil? or current_keys_hash_cache != downcase_hash
|
186
|
+
downcase_hash = current_keys_hash_cache
|
187
|
+
@current_keys_downcase = current_keys.map(&:downcase)
|
188
|
+
else
|
189
|
+
@current_keys_downcase
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
attr_writer :filter_ommisions
|
195
|
+
|
196
|
+
#
|
197
|
+
# @param other
|
198
|
+
# @return [TrueClass || FalseClass]
|
199
|
+
def allow_release?(keycode_or_keysym)
|
200
|
+
lower_kork = keycode_or_keysym.downcase
|
201
|
+
if filter_ommisions.include? lower_kork
|
202
|
+
filter_ommisions.delete(lower_kork)
|
203
|
+
on?
|
204
|
+
else
|
205
|
+
off?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# @return [Array]
|
211
|
+
def filter_ommisions
|
212
|
+
@filter_ommisions ||= []
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#
|
2
|
+
# fires after every example_group
|
3
|
+
module AfterEachExampleGroup
|
4
|
+
def after_each_example_group?
|
5
|
+
@after_each_example_group_flag ||= false
|
6
|
+
end
|
7
|
+
|
8
|
+
def after_each_example_group(&block)
|
9
|
+
if block
|
10
|
+
@after_each_example_group_flag = true
|
11
|
+
@after_each_example_group = block
|
12
|
+
else
|
13
|
+
@after_each_example_group
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
|
20
|
+
config.before :all do |example_group|
|
21
|
+
if ((egc = example_group.class).respond_to? :after_each_example_group? and egc.after_each_example_group?)
|
22
|
+
(egc.descendants - [egc]).map do |eg|
|
23
|
+
eg.append_after(:context, &egc.after_each_example_group)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
config.extend AfterEachExampleGroup
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
|
3
|
+
config.before :all do |example_group|
|
4
|
+
klass = example_group.class
|
5
|
+
|
6
|
+
(klass.descendants - [klass]).each do |egc|
|
7
|
+
if egc.metadata.key?(:clean_up_test_cache)
|
8
|
+
require 'fileutils'
|
9
|
+
require 'vigilem/evdev/key_map_cache'
|
10
|
+
test_name = 'key_map_cache_test'
|
11
|
+
egc.before(:each) do
|
12
|
+
Vigilem::Evdev::KeyMapCache.default_path = nil
|
13
|
+
Vigilem::Evdev::KeyMapCache.default_filename = test_name
|
14
|
+
end
|
15
|
+
egc.after(:each) do
|
16
|
+
Vigilem::Evdev::KeyMapCache.default_path = nil
|
17
|
+
Vigilem::Evdev::KeyMapCache.default_filename = nil
|
18
|
+
pth = File.join(Bundler.root, 'data', test_name)
|
19
|
+
FileUtils.remove(pth) if File.exists?(pth)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
event_files = Dir["/dev/input/event*"]
|
2
|
+
|
3
|
+
if event_files.empty?
|
4
|
+
puts %q<can't find character devices in /dev/input/event*>
|
5
|
+
exit
|
6
|
+
elsif not event_files.any? {|f| File.readable?(f) }
|
7
|
+
puts "Your user account isn't allowed to read from /dev/input/event*. please rerun as sudo"
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'bundler'
|
12
|
+
Bundler.setup
|
13
|
+
|
14
|
+
require 'timeout'
|
15
|
+
|
16
|
+
require 'inline'
|
17
|
+
|
18
|
+
require 'vigilem/support/core_ext/debug_puts'
|
19
|
+
|
20
|
+
require 'vigilem/support/patch/ffi/pointer'
|
21
|
+
|
22
|
+
require 'after_each_example_group'
|
23
|
+
|
24
|
+
require 'delete_test_cache_after_group'
|
25
|
+
|
26
|
+
$ver = `uname -r`.split('-').first
|
27
|
+
|
28
|
+
$major = (ary = $ver.split('.')).first.to_i
|
29
|
+
|
30
|
+
$minor = ary[1].to_i
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'vigilem/evdev/context_filter'
|
2
|
+
|
3
|
+
describe Vigilem::Evdev::ContextFilter do
|
4
|
+
|
5
|
+
class ContextFilterHost
|
6
|
+
include Vigilem::Evdev::ContextFilter
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { ContextFilterHost.new }
|
10
|
+
|
11
|
+
describe '::included' do
|
12
|
+
it 'extends Forwardable' do
|
13
|
+
expect(subject.class.singleton_class.included_modules).to include(Forwardable)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#last_known_state' do
|
18
|
+
it 'defaults to nil/UKNOWN' do
|
19
|
+
expect(subject.last_known_state).to be_nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#was_on?' do
|
24
|
+
it 'compares ON to #last_known_state defaulting to false' do
|
25
|
+
expect(subject.was_on?).to be_falsey
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns true if #last_known_state == ON' do
|
29
|
+
subject.send(:last_known_state=, described_class::ON)
|
30
|
+
expect(subject.was_on?).to be_truthy
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#was_off?' do
|
35
|
+
it 'compares OFF to #last_known_state defaulting to true' do
|
36
|
+
expect(subject.was_off?).to be_truthy
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns true if #last_known_state == OFF' do
|
40
|
+
subject.send(:last_known_state=, described_class::ON)
|
41
|
+
expect(subject.was_off?).to be_falsey
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#on?' do
|
46
|
+
it 'raises error because it needs to be overridden' do
|
47
|
+
expect { subject.on? }.to raise_error(NotImplementedError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#alias_method :filtered?, :on?
|
52
|
+
|
53
|
+
|
54
|
+
describe '#off?' do
|
55
|
+
it 'raises error because it needs to be overridden' do
|
56
|
+
expect { subject.off? }.to raise_error(NotImplementedError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#on_change' do
|
61
|
+
it 'updates #last_known_state' do
|
62
|
+
subject.on_change(described_class::ON)
|
63
|
+
expect(subject.last_known_state).to eql(described_class::ON)
|
64
|
+
end
|
65
|
+
|
66
|
+
class FakeObserver
|
67
|
+
def update(type, value)
|
68
|
+
(@updates ||= []).concat([type, value])
|
69
|
+
end
|
70
|
+
attr_reader :updates
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'notifies observers' do
|
74
|
+
fo = FakeObserver.new
|
75
|
+
subject.add_observer(fo)
|
76
|
+
subject.on_change(described_class::ON)
|
77
|
+
expect(fo.updates).to eql([subject, described_class::ON])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'private' do
|
82
|
+
describe '#last_known_state=' do
|
83
|
+
it 'sets #last_known_state' do
|
84
|
+
subject.send(:last_known_state=, described_class::ON)
|
85
|
+
expect(subject.last_known_state).to eql(described_class::ON)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#was_on=' do
|
90
|
+
it 'sets #was_on?' do
|
91
|
+
subject.send(:was_on=, false)
|
92
|
+
expect(subject.was_on?).to be_falsey
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'updates #last_known_state' do
|
96
|
+
subject.send(:was_on=, true)
|
97
|
+
expect(subject.last_known_state).to eql(described_class::ON)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#was_off=' do
|
102
|
+
it 'sets #was_off?' do
|
103
|
+
subject.send(:was_off=, true)
|
104
|
+
expect(subject.was_off?).to be_truthy
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'updates #last_known_state' do
|
108
|
+
subject.send(:was_off=, true)
|
109
|
+
expect(subject.last_known_state).to eql(described_class::OFF)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|