Supporting a button library.

This commit is contained in:
Eric Ratliff
2026-02-22 07:44:41 -06:00
parent 131ca648b5
commit e235db504d
9 changed files with 922 additions and 0 deletions

View File

@@ -615,4 +615,214 @@ fn test_add_remove_pin_assignment_survives() {
board_pins.assignments.contains_key("tmp36_data"),
"Pin assignment should survive library removal"
);
}
// ==========================================================================
// Button Library: registry, extraction, content, coexistence
// ==========================================================================
#[test]
fn test_library_registry_lists_button() {
let libs = library::list_available();
let btn = libs.iter().find(|l| l.name == "button");
assert!(btn.is_some(), "Button should be in the registry");
let meta = btn.unwrap();
assert_eq!(meta.bus, "digital");
assert_eq!(meta.pins, vec!["signal"]);
assert_eq!(meta.interface, "button.h");
assert_eq!(meta.mock, "button_mock.h");
}
#[test]
fn test_button_extract_creates_driver_directory() {
let tmp = TempDir::new().unwrap();
let written = library::extract_library("button", tmp.path()).unwrap();
assert!(!written.is_empty(), "Should write files");
let driver_dir = tmp.path().join("lib/drivers/button");
assert!(driver_dir.exists(), "Driver directory should be created");
assert!(driver_dir.join("button.h").exists(), "Interface header");
assert!(driver_dir.join("button_digital.h").exists(), "Implementation");
assert!(driver_dir.join("button_mock.h").exists(), "Mock");
assert!(driver_dir.join("button_sim.h").exists(), "Simulation");
}
#[test]
fn test_button_extract_files_content_is_valid() {
let tmp = TempDir::new().unwrap();
library::extract_library("button", tmp.path()).unwrap();
let driver_dir = tmp.path().join("lib/drivers/button");
// Interface should define Button class
let interface = fs::read_to_string(driver_dir.join("button.h")).unwrap();
assert!(interface.contains("class Button"), "Should define Button");
assert!(interface.contains("isPressed"), "Should declare isPressed");
assert!(interface.contains("readState"), "Should declare readState");
// Implementation should include hal.h
let impl_h = fs::read_to_string(driver_dir.join("button_digital.h")).unwrap();
assert!(impl_h.contains("hal.h"), "Implementation should use HAL");
assert!(impl_h.contains("class ButtonDigital"), "Should define ButtonDigital");
assert!(impl_h.contains("digitalRead"), "Should use digitalRead");
// Mock should have setPressed
let mock_h = fs::read_to_string(driver_dir.join("button_mock.h")).unwrap();
assert!(mock_h.contains("class ButtonMock"), "Should define ButtonMock");
assert!(mock_h.contains("setPressed"), "Mock should have setPressed");
// Sim should have press/release and bounce
let sim_h = fs::read_to_string(driver_dir.join("button_sim.h")).unwrap();
assert!(sim_h.contains("class ButtonSim"), "Should define ButtonSim");
assert!(sim_h.contains("setBounceReads"), "Sim should have setBounceReads");
assert!(sim_h.contains("press()"), "Sim should have press()");
assert!(sim_h.contains("release()"), "Sim should have release()");
}
#[test]
fn test_button_files_are_ascii_only() {
let tmp = TempDir::new().unwrap();
library::extract_library("button", tmp.path()).unwrap();
let driver_dir = tmp.path().join("lib/drivers/button");
for entry in fs::read_dir(&driver_dir).unwrap() {
let entry = entry.unwrap();
let content = fs::read_to_string(entry.path()).unwrap();
for (line_num, line) in content.lines().enumerate() {
for (col, ch) in line.chars().enumerate() {
assert!(
ch.is_ascii(),
"Non-ASCII in {} at {}:{}: U+{:04X}",
entry.file_name().to_string_lossy(),
line_num + 1, col + 1, ch as u32
);
}
}
}
}
#[test]
fn test_button_remove_cleans_up() {
let tmp = TempDir::new().unwrap();
library::extract_library("button", tmp.path()).unwrap();
assert!(library::is_installed_on_disk("button", tmp.path()));
assert!(tmp.path().join("test/test_button.cpp").exists());
library::remove_library_files("button", tmp.path()).unwrap();
assert!(!library::is_installed_on_disk("button", tmp.path()));
assert!(!tmp.path().join("test/test_button.cpp").exists());
}
#[test]
fn test_button_meta_wiring_summary() {
let meta = library::find_library("button").unwrap();
let summary = meta.wiring_summary();
assert!(summary.contains("digital"), "Should mention digital bus: {}", summary);
}
#[test]
fn test_button_meta_pin_roles() {
let meta = library::find_library("button").unwrap();
let roles = meta.pin_roles();
assert_eq!(roles.len(), 1);
assert_eq!(roles[0].0, "signal");
assert_eq!(roles[0].1, "button_signal");
}
#[test]
fn test_button_meta_default_mode() {
let meta = library::find_library("button").unwrap();
assert_eq!(meta.default_mode(), "input");
}
#[test]
fn test_button_interface_uses_polymorphism() {
let tmp = TempDir::new().unwrap();
library::extract_library("button", tmp.path()).unwrap();
let driver_dir = tmp.path().join("lib/drivers/button");
// All implementations should inherit from Button
let impl_h = fs::read_to_string(driver_dir.join("button_digital.h")).unwrap();
assert!(impl_h.contains(": public Button"), "ButtonDigital should inherit Button");
let mock_h = fs::read_to_string(driver_dir.join("button_mock.h")).unwrap();
assert!(mock_h.contains(": public Button"), "ButtonMock should inherit Button");
let sim_h = fs::read_to_string(driver_dir.join("button_sim.h")).unwrap();
assert!(sim_h.contains(": public Button"), "ButtonSim should inherit Button");
}
#[test]
fn test_add_button_full_flow() {
let tmp = TempDir::new().unwrap();
let ctx = TemplateContext {
project_name: "btn_flow".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
let meta = library::find_library("button").unwrap();
library::extract_library("button", tmp.path()).unwrap();
let mut config = ProjectConfig::load(tmp.path()).unwrap();
let driver_include = format!("lib/drivers/{}", meta.name);
config.build.include_dirs.push(driver_include);
config.libraries.insert(meta.name.clone(), meta.version.clone());
config.save(tmp.path()).unwrap();
// Assign a digital pin
let dir_str = tmp.path().to_string_lossy().to_string();
commands::pin::assign_pin(
"button_signal", "2",
Some("input"),
None,
Some(&dir_str),
).unwrap();
let config_after = ProjectConfig::load(tmp.path()).unwrap();
assert!(config_after.libraries.contains_key("button"));
let board_pins = config_after.pins.get("uno").unwrap();
assert!(board_pins.assignments.contains_key("button_signal"));
let assignment = &board_pins.assignments["button_signal"];
assert_eq!(assignment.mode, "input");
}
#[test]
fn test_both_libraries_install_together() {
let tmp = TempDir::new().unwrap();
let ctx = TemplateContext {
project_name: "both_libs".to_string(),
anvil_version: "1.0.0".to_string(),
board_name: "uno".to_string(),
fqbn: "arduino:avr:uno".to_string(),
baud: 115200,
};
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
library::extract_library("tmp36", tmp.path()).unwrap();
library::extract_library("button", tmp.path()).unwrap();
// Both driver directories exist
assert!(tmp.path().join("lib/drivers/tmp36").is_dir());
assert!(tmp.path().join("lib/drivers/button").is_dir());
// Both test files exist
assert!(tmp.path().join("test/test_tmp36.cpp").exists());
assert!(tmp.path().join("test/test_button.cpp").exists());
// Remove button, tmp36 survives
library::remove_library_files("button", tmp.path()).unwrap();
assert!(!library::is_installed_on_disk("button", tmp.path()));
assert!(library::is_installed_on_disk("tmp36", tmp.path()));
assert!(tmp.path().join("test/test_tmp36.cpp").exists());
assert!(!tmp.path().join("test/test_button.cpp").exists());
}