use crate::link::reconcile::{DesiredState, clip_packet_key, clip_trigger_key};
use crate::types::{
Action, Blanket, Button, ClipAction, ClipPacketTrigger, ClipPacketTriggerEntry, ClipSettings,
ClipState, ClipStatus, ClipTrigger, Direction, Edge, Key, MediaKey, TrafficClass, Usage,
};
#[test]
fn default_is_idle() {
let d = DesiredState::default();
assert!(d.is_idle());
assert_eq!(d.held().count(), 0);
}
#[test]
fn press_is_held_and_non_idle() {
let mut d = DesiredState::default();
d.apply(Button::LEFT.into(), Action::Press);
assert!(!d.is_idle());
assert_eq!(
d.held().collect::<Vec<_>>(),
vec![(Usage::from(Button::LEFT), Action::Press)]
);
}
#[test]
fn force_release_is_held() {
let mut d = DesiredState::default();
d.apply(Button::RIGHT.into(), Action::ForceRelease);
assert!(!d.is_idle());
assert_eq!(
d.held().collect::<Vec<_>>(),
vec![(Usage::from(Button::RIGHT), Action::ForceRelease)]
);
}
#[test]
fn soft_release_clears_the_override() {
let mut d = DesiredState::default();
d.apply(Button::MIDDLE.into(), Action::Press);
d.apply(Button::MIDDLE.into(), Action::SoftRelease);
assert!(d.is_idle());
}
#[test]
fn clear_resets_all() {
let mut d = DesiredState::default();
d.apply(Button::LEFT.into(), Action::Press);
d.apply(Button::SIDE2.into(), Action::ForceRelease);
assert!(!d.is_idle());
d.clear();
assert!(d.is_idle());
assert_eq!(d.held().count(), 0);
}
#[test]
fn held_preserves_identity_in_class_then_id_order() {
let mut d = DesiredState::default();
d.apply(Button::LEFT.into(), Action::Press);
d.apply(Button::SIDE1.into(), Action::ForceRelease);
assert_eq!(
d.held().collect::<Vec<_>>(),
vec![
(Usage::from(Button::LEFT), Action::Press),
(Usage::from(Button::SIDE1), Action::ForceRelease),
]
);
}
#[test]
fn key_press_is_held_and_non_idle() {
let mut d = DesiredState::default();
d.apply(Key::A.into(), Action::Press);
assert!(!d.is_idle());
assert_eq!(
d.held().collect::<Vec<_>>(),
vec![(Usage::from(Key::A), Action::Press)]
);
}
#[test]
fn key_soft_release_clears_the_override() {
let mut d = DesiredState::default();
d.apply(Key::LEFT_SHIFT.into(), Action::Press);
d.apply(Key::LEFT_SHIFT.into(), Action::SoftRelease);
assert!(d.is_idle());
assert_eq!(d.held().count(), 0);
}
#[test]
fn media_press_is_held() {
let mut d = DesiredState::default();
d.apply(MediaKey::VOLUME_UP.into(), Action::Press);
assert!(!d.is_idle());
assert_eq!(
d.held().collect::<Vec<_>>(),
vec![(Usage::from(MediaKey::VOLUME_UP), Action::Press)]
);
}
#[test]
fn media_soft_release_clears_the_override() {
let mut d = DesiredState::default();
d.apply(MediaKey::MUTE.into(), Action::Press);
d.apply(MediaKey::MUTE.into(), Action::SoftRelease);
assert!(d.is_idle());
}
#[test]
fn one_store_holds_every_class_and_orders_by_class_then_id() {
let mut d = DesiredState::default();
d.apply(MediaKey::VOLUME_UP.into(), Action::Press);
d.apply(Button::LEFT.into(), Action::Press);
d.apply(Key::A.into(), Action::Press);
assert_eq!(
d.held().map(|(u, _)| u).collect::<Vec<_>>(),
vec![
Usage::from(Button::LEFT),
Usage::from(Key::A),
Usage::from(MediaKey::VOLUME_UP),
]
);
d.clear();
assert!(d.is_idle());
assert_eq!(d.held().count(), 0);
}
#[test]
fn idle_requires_every_class_empty() {
let mut d = DesiredState::default();
d.apply(Key::ESCAPE.into(), Action::Press);
assert!(!d.is_idle());
d.apply(Key::ESCAPE.into(), Action::SoftRelease);
assert!(d.is_idle());
d.apply(MediaKey::PLAY_PAUSE.into(), Action::ForceRelease);
assert!(!d.is_idle());
}
use crate::protocol::opcode::{
LOCK_AXIS_X, LOCK_CLS_AXIS, LOCK_CLS_BTN, LOCK_CLS_KEY, LOCK_DIR_AGAINST, LOCK_DIR_BOTH,
LOCK_DIR_NEG, LOCK_DIR_POS, LOCK_DIR_WITH, LOCK_ID_ALL, LOCK_SCALE_BLOCK, LOCK_SCALE_PASS,
};
const X: (u8, u16) = (LOCK_CLS_AXIS, LOCK_AXIS_X);
#[test]
fn releasing_one_sign_of_a_both_lock_leaves_the_other() {
let mut d = DesiredState::default();
d.apply_lock((X.0, X.1, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.apply_lock((X.0, X.1, LOCK_DIR_NEG), LOCK_SCALE_PASS);
assert_eq!(
d.held_locks(),
vec![((X.0, X.1, LOCK_DIR_POS), LOCK_SCALE_BLOCK)]
);
d.apply_lock((X.0, X.1, LOCK_DIR_POS), LOCK_SCALE_PASS);
assert!(d.is_idle());
}
#[test]
fn a_both_write_replaces_the_single_directions_before_it() {
let mut d = DesiredState::default();
d.apply_lock((X.0, X.1, LOCK_DIR_NEG), 40);
d.apply_lock((X.0, X.1, LOCK_DIR_AGAINST), 30);
d.apply_lock((X.0, X.1, LOCK_DIR_BOTH), 50);
assert_eq!(d.held_locks(), vec![((X.0, X.1, LOCK_DIR_BOTH), 50)]);
}
#[test]
fn a_relative_scale_survives_an_absolute_one() {
let mut d = DesiredState::default();
d.apply_lock((X.0, X.1, LOCK_DIR_AGAINST), 40);
d.apply_lock((X.0, X.1, LOCK_DIR_POS), 60);
assert_eq!(
d.held_locks(),
vec![
((X.0, X.1, LOCK_DIR_POS), 60),
((X.0, X.1, LOCK_DIR_AGAINST), 40),
]
);
}
#[test]
fn a_full_unlock_clears_every_slot() {
let mut d = DesiredState::default();
d.apply_lock((X.0, X.1, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.apply_lock((X.0, X.1, LOCK_DIR_WITH), 30);
assert!(!d.is_idle());
d.apply_lock((X.0, X.1, LOCK_DIR_BOTH), LOCK_SCALE_PASS);
assert!(d.is_idle());
assert_eq!(d.held_locks(), vec![]);
}
#[test]
fn a_one_bit_class_holds_what_the_box_will_hold() {
let mut d = DesiredState::default();
d.apply_lock((LOCK_CLS_BTN, 0, LOCK_DIR_POS), 150);
assert!(d.is_idle());
d.apply_lock((LOCK_CLS_BTN, 0, LOCK_DIR_POS), 50);
assert_eq!(
d.held_locks(),
vec![((LOCK_CLS_BTN, 0, LOCK_DIR_POS), LOCK_SCALE_BLOCK)]
);
d.apply_lock((X.0, X.1, LOCK_DIR_POS), 150);
assert!(d.held_locks().contains(&((X.0, X.1, LOCK_DIR_POS), 150)));
}
#[test]
fn a_button_blanket_widens_to_the_declared_count_when_caps_arrives() {
let mut d = DesiredState::default();
d.apply_lock((LOCK_CLS_BTN, LOCK_ID_ALL, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
assert_eq!(d.held_locks().len(), 5);
d.note_declared_buttons(8);
let ids: Vec<u16> = d.held_locks().iter().map(|&((_, id, _), _)| id).collect();
assert_eq!(ids, (0..8).collect::<Vec<u16>>());
d.apply_lock((LOCK_CLS_BTN, 0, LOCK_DIR_BOTH), LOCK_SCALE_PASS);
let held = d.held_locks();
assert_eq!(held.len(), 7);
assert!(!held.iter().any(|&((_, id, _), _)| id == 0));
}
#[test]
fn a_button_blanket_re_expands_when_the_declared_count_changes() {
let mut d = DesiredState::default();
d.note_declared_buttons(8);
d.apply_lock((LOCK_CLS_BTN, LOCK_ID_ALL, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
assert_eq!(d.held_locks().len(), 8);
d.note_declared_buttons(12);
assert_eq!(d.held_locks().len(), 12);
d.note_declared_buttons(4);
assert_eq!(d.held_locks().len(), 4);
}
#[test]
fn an_undone_button_release_restores_the_blanket() {
let mut d = DesiredState::default();
d.note_declared_buttons(8);
d.apply_lock((LOCK_CLS_BTN, LOCK_ID_ALL, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
let before = d.held_locks();
let undo = d.apply_lock((LOCK_CLS_BTN, 0, LOCK_DIR_BOTH), LOCK_SCALE_PASS);
assert_eq!(d.held_locks().len(), 7); d.restore_lock(undo);
assert_eq!(d.held_locks(), before);
}
#[test]
fn a_button_blanket_expands_onto_the_declared_count() {
let mut d = DesiredState::default();
d.note_declared_buttons(16);
d.apply_lock((LOCK_CLS_BTN, LOCK_ID_ALL, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
let ids: Vec<u16> = d.held_locks().iter().map(|&((_, id, _), _)| id).collect();
assert_eq!(ids, (0..16).collect::<Vec<u16>>());
assert!(
d.held_locks()
.iter()
.any(|&((cls, id, _), _)| cls == LOCK_CLS_BTN && id == 8)
);
}
#[test]
fn a_button_blanket_caps_at_the_box_ceiling() {
let mut d = DesiredState::default();
d.note_declared_buttons(40);
d.apply_lock((LOCK_CLS_BTN, LOCK_ID_ALL, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
assert_eq!(d.held_locks().len(), 16);
}
#[test]
fn a_key_blanket_is_its_own_row() {
let mut d = DesiredState::default();
d.apply_lock((LOCK_CLS_KEY, LOCK_ID_ALL, LOCK_DIR_POS), LOCK_SCALE_BLOCK);
assert_eq!(
d.held_locks(),
vec![((LOCK_CLS_KEY, LOCK_ID_ALL, LOCK_DIR_POS), LOCK_SCALE_BLOCK)]
);
}
use crate::protocol::opcode::LOCK_CLS_MEDIA;
fn media_ids(d: &DesiredState) -> Vec<u16> {
d.held_locks()
.iter()
.filter(|&&((class, _, _), _)| class == LOCK_CLS_MEDIA)
.map(|&((_, id, _), _)| id)
.collect()
}
#[test]
fn media_locks_replay_in_the_order_they_were_taken() {
let mut d = DesiredState::default();
let taken = [0x223u16, 0x30, 0xB5, 0xE9, 0xB6, 0xCD, 0xE2, 0xB7, 0xEA];
for id in taken {
d.apply_lock((LOCK_CLS_MEDIA, id, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
}
assert_eq!(media_ids(&d), taken);
}
#[test]
fn a_released_media_lock_leaves_the_order_of_the_rest() {
let mut d = DesiredState::default();
for id in [0xEAu16, 0xE9, 0x30] {
d.apply_lock((LOCK_CLS_MEDIA, id, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
}
d.apply_lock((LOCK_CLS_MEDIA, 0xE9, LOCK_DIR_BOTH), LOCK_SCALE_PASS);
d.apply_lock((LOCK_CLS_MEDIA, 0xB5, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
assert_eq!(media_ids(&d), vec![0xEA, 0x30, 0xB5]);
d.apply_lock((LOCK_CLS_MEDIA, 0xEA, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
assert_eq!(media_ids(&d), vec![0xEA, 0x30, 0xB5]);
}
#[test]
fn a_media_blanket_does_not_take_a_slot() {
let mut d = DesiredState::default();
d.apply_lock((LOCK_CLS_MEDIA, 0xEA, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.apply_lock(
(LOCK_CLS_MEDIA, LOCK_ID_ALL, LOCK_DIR_BOTH),
LOCK_SCALE_BLOCK,
);
d.apply_lock((LOCK_CLS_MEDIA, 0x30, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
assert_eq!(media_ids(&d), vec![0xEA, 0x30, LOCK_ID_ALL]);
}
#[test]
fn every_other_class_still_replays_in_id_order() {
let mut d = DesiredState::default();
for id in [3u16, 0, 1] {
d.apply_lock((LOCK_CLS_BTN, id, LOCK_DIR_POS), LOCK_SCALE_BLOCK);
}
assert_eq!(
d.held_locks()
.iter()
.map(|&((_, id, _), _)| id)
.collect::<Vec<_>>(),
vec![0, 1, 3]
);
}
#[test]
fn an_undone_apply_leaves_the_state_exactly_as_it_was() {
let mut d = DesiredState::default();
d.apply_lock((X.0, X.1, LOCK_DIR_BOTH), 40);
d.apply_lock((LOCK_CLS_MEDIA, 0xEA, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
let before = (d.held_locks(), d.is_idle());
let undo = d.apply_lock((X.0, X.1, LOCK_DIR_AGAINST), LOCK_SCALE_BLOCK);
d.restore_lock(undo);
assert_eq!((d.held_locks(), d.is_idle()), before);
let undo = d.apply_lock((LOCK_CLS_BTN, LOCK_ID_ALL, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.restore_lock(undo);
let undo = d.apply_lock((LOCK_CLS_MEDIA, 0x30, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.restore_lock(undo);
assert_eq!((d.held_locks(), d.is_idle()), before);
let undo = d.apply_lock((X.0, X.1, LOCK_DIR_BOTH), LOCK_SCALE_PASS);
d.restore_lock(undo);
assert_eq!((d.held_locks(), d.is_idle()), before);
}
#[test]
fn a_row_of_another_class_never_disturbs_the_media_order() {
let mut d = DesiredState::default();
d.apply_lock((LOCK_CLS_MEDIA, 3, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.apply_lock((LOCK_CLS_MEDIA, 0xEA, LOCK_DIR_BOTH), LOCK_SCALE_BLOCK);
d.apply_lock((LOCK_CLS_BTN, 3, LOCK_DIR_POS), LOCK_SCALE_BLOCK);
d.apply_lock((LOCK_CLS_BTN, 3, LOCK_DIR_POS), LOCK_SCALE_PASS);
assert_eq!(media_ids(&d), vec![3, 0xEA]);
}
use crate::link::reconcile::StoredRewrite;
fn stored(class: u8, id: u16) -> StoredRewrite {
StoredRewrite {
class,
id,
direction: 0,
action: 1,
offset: 0,
match_bytes: vec![],
mask: vec![],
payload: vec![],
}
}
#[test]
fn rewrite_rule_is_held_and_non_idle() {
let mut d = DesiredState::default();
d.apply_rewrite(stored(9, 1));
assert!(!d.is_idle());
assert_eq!(d.held_rewrites().len(), 1);
}
#[test]
fn rewrite_overwrite_keeps_one_row() {
let mut d = DesiredState::default();
d.apply_rewrite(stored(9, 1));
let mut r = stored(9, 1);
r.action = 3; d.apply_rewrite(r);
let held = d.held_rewrites();
assert_eq!(held.len(), 1);
assert_eq!(held[0].action, 3);
}
#[test]
fn rewrite_remove_and_restore() {
let mut d = DesiredState::default();
d.apply_rewrite(stored(9, 1));
let key = stored(9, 1).key();
let undo = d.remove_rewrite(key);
assert!(d.held_rewrites().is_empty());
d.restore_rewrite(undo); assert_eq!(d.held_rewrites().len(), 1);
}
#[test]
fn rewrite_apply_undo_puts_it_back() {
let mut d = DesiredState::default();
let undo = d.apply_rewrite(stored(9, 1));
d.restore_rewrite(undo);
assert!(d.held_rewrites().is_empty());
assert!(d.is_idle());
}
#[test]
fn clear_rewrites_empties_the_table() {
let mut d = DesiredState::default();
d.apply_rewrite(stored(9, 1));
d.apply_rewrite(stored(4, 0));
d.clear_rewrites();
assert!(d.held_rewrites().is_empty());
}
#[test]
fn reset_clears_rewrites_too() {
let mut d = DesiredState::default();
d.apply_rewrite(stored(9, 1));
d.clear(); assert!(d.held_rewrites().is_empty());
assert!(d.is_idle());
}
use crate::link::reconcile::StoredTransform;
fn stored_xf(sclass: u8, sid: u16, dclass: u8, did: u16) -> StoredTransform {
StoredTransform {
op: 0, sclass,
sid,
dclass,
did,
}
}
#[test]
fn transform_is_held_and_non_idle() {
let mut d = DesiredState::default();
d.apply_transform(stored_xf(3, 1, 3, 1));
assert!(!d.is_idle());
assert_eq!(d.held_transforms().len(), 1);
}
#[test]
fn transform_overwrite_keeps_one_row() {
let mut d = DesiredState::default();
d.apply_transform(stored_xf(3, 1, 3, 1));
let mut r = stored_xf(3, 1, 3, 1);
r.op = 1; d.apply_transform(r);
let held = d.held_transforms();
assert_eq!(held.len(), 1);
assert_eq!(held[0].op, 1);
}
#[test]
fn transform_remove_and_restore() {
let mut d = DesiredState::default();
d.apply_transform(stored_xf(3, 1, 3, 1));
let key = stored_xf(3, 1, 3, 1).key();
let undo = d.remove_transform(key);
assert!(d.held_transforms().is_empty());
d.restore_transform(undo); assert_eq!(d.held_transforms().len(), 1);
}
#[test]
fn transform_apply_undo_puts_it_back() {
let mut d = DesiredState::default();
let undo = d.apply_transform(stored_xf(3, 1, 3, 1));
d.restore_transform(undo);
assert!(d.held_transforms().is_empty());
assert!(d.is_idle());
}
#[test]
fn clear_transforms_empties_the_table() {
let mut d = DesiredState::default();
d.apply_transform(stored_xf(3, 0, 3, 0));
d.apply_transform(stored_xf(3, 1, 3, 1));
d.clear_transforms();
assert!(d.held_transforms().is_empty());
}
#[test]
fn reset_clears_transforms_too() {
let mut d = DesiredState::default();
d.apply_transform(stored_xf(3, 1, 3, 1));
d.clear(); assert!(d.held_transforms().is_empty());
assert!(d.is_idle());
}
#[test]
fn a_loaded_clip_a_setting_or_a_trigger_is_not_idle() {
let mut d = DesiredState::default();
d.clip_loaded(true);
assert!(!d.is_idle());
d.clip_loaded(false);
assert!(d.is_idle());
d.clip_setting(2, 1); assert!(!d.is_idle());
d.clip_setting(2, 0);
assert!(d.is_idle());
d.clip_setting(9, 1); assert!(d.is_idle());
let on = |edge| ClipTrigger::new(Key::new(0x3A), edge, ClipAction::Start);
d.clip_bind(on(Edge::Press));
d.clip_bind(on(Edge::Release));
d.clip_unbind(&clip_trigger_key(&on(Edge::Press)));
assert!(!d.is_idle());
d.clip_triggers_clear();
assert!(d.is_idle());
let t = |mask: u8| {
ClipPacketTrigger::new(TrafficClass::HidIn, 2, Direction::IN, ClipAction::Start)
.matching([0x07], [mask])
};
d.clip_packet_bind(&t(0xFF));
d.clip_packet_bind(&t(0x0F));
d.clip_packet_unbind(&clip_packet_key(&t(0xFF)));
assert!(!d.is_idle());
d.clip_packet_unbind(&clip_packet_key(&t(0x0F)));
assert!(d.is_idle());
d.clip_bind(on(Edge::Press));
d.clip_packet_bind(&t(0xFF));
d.clip_triggers_clear();
assert!(d.is_idle());
d.clip_packet_bind(&t(0xFF));
d.clip_triggers_clear();
assert!(d.is_idle());
}
#[test]
fn the_opt_in_going_off_drops_the_consuming_packet_triggers() {
let t = |id: u16, consume: bool| ClipPacketTrigger {
consume,
..ClipPacketTrigger::new(TrafficClass::HidIn, id, Direction::IN, ClipAction::Start)
.matching([0x07], [0xFF])
};
let mut d = DesiredState::default();
d.clip_packet_bind(&t(1, true));
d.clip_packet_bind(&t(2, false));
d.clip_packet_bind(&t(3, true));
assert_eq!(d.clip_packet_drop_consuming(), vec![t(1, true), t(3, true)]);
assert!(!d.is_idle(), "the watching trigger stands");
assert_eq!(d.clip_packet_drop_consuming(), vec![]);
d.clip_packet_unbind(&clip_packet_key(&t(2, false)));
assert!(d.is_idle());
d.clip_packet_bind(&t(1, true));
d.clip_packet_bind(&t(1, false));
assert_eq!(d.clip_packet_drop_consuming(), vec![]);
assert!(!d.is_idle());
d.clip_packet_bind(&t(1, true));
d.clip_packet_drop_consuming();
assert!(d.is_idle());
let adopted = |consume: bool| ClipSettings {
packet_triggers: vec![ClipPacketTriggerEntry {
trigger: ClipPacketTrigger {
consume,
..ClipPacketTrigger::new(TrafficClass::HidIn, 2, Direction::IN, ClipAction::Start)
},
hits: 0,
}],
..ClipSettings::default()
};
for consume in [false, true] {
let mut d = DesiredState::default();
d.clip_adopt(&ClipStatus::default(), &adopted(consume));
assert_eq!(d.clip_packet_drop_consuming().len(), consume as usize);
assert_eq!(d.is_idle(), consume);
}
}
#[test]
fn a_reset_forgets_the_clip() {
let mut d = DesiredState::default();
d.clip_loaded(true);
d.clip_setting(1, 1);
d.clip_bind(ClipTrigger::new(
Button::SIDE1,
Edge::Press,
ClipAction::Start,
));
d.clip_packet_bind(&ClipPacketTrigger::new(
TrafficClass::Emit,
1,
Direction::IN,
ClipAction::Start,
));
d.clear();
assert!(d.is_idle());
}
#[test]
fn a_reconnect_adopts_what_the_box_still_holds_of_a_clip() {
let empty = ClipStatus::default();
let plain = ClipSettings::default();
let mut d = DesiredState::default();
d.clip_loaded(true);
d.clip_setting(3, 1);
d.clip_bind(ClipTrigger::new(
Button::SIDE1,
Edge::Press,
ClipAction::Start,
));
d.clip_packet_bind(&ClipPacketTrigger::new(
TrafficClass::Emit,
1,
Direction::IN,
ClipAction::Start,
));
d.clip_adopt(&empty, &plain);
assert!(
d.is_idle(),
"a box that lost the clip leaves nothing to keep alive"
);
let held = |status: &ClipStatus, settings: &ClipSettings| {
let mut d = DesiredState::default();
d.clip_adopt(status, settings);
!d.is_idle()
};
assert!(held(
&ClipStatus {
total: 40,
..empty.clone()
},
&plain
));
assert!(held(
&ClipStatus {
state: ClipState::Playing,
..empty.clone()
},
&plain
));
assert!(held(
&empty,
&ClipSettings {
autolock: vec![Blanket::Aim],
..plain.clone()
}
));
assert!(held(
&empty,
&ClipSettings {
loop_: true,
..plain.clone()
}
));
assert!(held(
&empty,
&ClipSettings {
retain: true,
..plain.clone()
}
));
assert!(held(
&empty,
&ClipSettings {
ride: true,
..plain.clone()
}
));
assert!(!held(
&empty,
&ClipSettings {
finalized: true,
..plain.clone()
}
));
for (id, only) in [
(
0u8,
ClipSettings {
autolock: vec![Blanket::Aim],
..plain.clone()
},
),
(
1,
ClipSettings {
loop_: true,
..plain.clone()
},
),
(
2,
ClipSettings {
retain: true,
..plain.clone()
},
),
(
3,
ClipSettings {
ride: true,
..plain.clone()
},
),
] {
let mut d = DesiredState::default();
d.clip_adopt(&empty, &only);
for other in (0..4).filter(|&o| o != id) {
d.clip_setting(other, 0);
}
assert!(!d.is_idle(), "setting {id} was adopted onto another id");
d.clip_setting(id, 0);
assert!(d.is_idle(), "setting {id}");
}
let bound = ClipSettings {
triggers: vec![ClipTrigger::new(
Button::SIDE1,
Edge::Press,
ClipAction::Toggle,
)],
ride: true,
..plain.clone()
};
let mut d = DesiredState::default();
d.clip_adopt(&empty, &bound);
d.clip_setting(3, 0);
assert!(!d.is_idle());
let (class, id) = Usage::from(Button::SIDE1).class_id();
d.clip_unbind(&(class, id, 1));
assert!(d.is_idle());
let packet = ClipPacketTrigger::new(TrafficClass::HidIn, 2, Direction::IN, ClipAction::Start)
.matching([0x07, 0x20], [0xFF, 0x20])
.once_per_run(1);
let watching = ClipSettings {
packet_triggers: vec![ClipPacketTriggerEntry {
trigger: packet.clone(),
hits: 3,
}],
..plain.clone()
};
assert!(held(&empty, &watching));
let mut d = DesiredState::default();
d.clip_adopt(&empty, &watching);
for other in [
ClipPacketTrigger {
class: TrafficClass::Emit,
..packet.clone()
},
ClipPacketTrigger {
id: 3,
..packet.clone()
},
ClipPacketTrigger {
direction: Direction::OUT,
..packet.clone()
},
packet.clone().matching([0x07, 0x00], [0xFF, 0x20]),
packet.clone().matching([0x07, 0x20], [0xFF, 0xFF]),
] {
d.clip_packet_unbind(&clip_packet_key(&other));
assert!(!d.is_idle(), "{other:?}");
}
let same_key = ClipPacketTrigger {
action: ClipAction::Stop,
consume: true,
once_per_run: false,
selector_len: 0,
..packet
};
d.clip_packet_unbind(&clip_packet_key(&same_key));
assert!(d.is_idle());
}
#[test]
fn a_reconnect_that_finds_the_clip_gone_reports_it_lost() {
let gone = ClipStatus::default();
let kept = ClipStatus {
total: 40,
..ClipStatus::default()
};
let plain = ClipSettings::default();
for (loaded, status, lost) in [
(true, &gone, true),
(true, &kept, false),
(false, &gone, false),
] {
let mut d = DesiredState::default();
d.clip_loaded(loaded);
d.clip_adopt(status, &plain);
assert_eq!(
d.clip_lost(),
lost,
"loaded={loaded} total={}",
status.total
);
}
let mut d = DesiredState::default();
d.clip_loaded(true);
d.clip_adopt(&gone, &plain);
d.clip_adopt(&gone, &plain);
assert!(d.clip_lost());
d.clip_loaded(true);
assert!(!d.clip_lost());
}
#[test]
fn a_clip_is_lost_only_on_the_box_s_word_that_its_ring_went() {
let gone = ClipStatus::default();
let kept = ClipStatus {
total: 40,
..ClipStatus::default()
};
let released = |before: &ClipStatus, after: &ClipStatus| {
let mut d = DesiredState::default();
d.clip_loaded(true);
let seen = d.clip_ring_gen();
d.clip_note_ring(seen, before, false);
let seen = d.clip_ring_gen();
d.clip_note_ring(seen, after, true);
d.clip_lost()
};
assert!(released(&kept, &gone));
assert!(!released(&gone, &gone), "the box had already emptied it");
assert!(
!released(&kept, &kept),
"the release left the ring standing"
);
let mut d = DesiredState::default();
d.clip_loaded(true);
let seen = d.clip_ring_gen();
d.clip_loaded(true);
d.clip_note_ring(seen, &gone, true);
assert!(
!d.clip_lost(),
"a reading from before the last append is not trusted"
);
}