tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::super::*;
#[allow(clippy::wildcard_imports)]
use super::helpers::*;
use std::fs;

fn state(detail: &crate::ui::schedule::CapacityDetail) -> &str {
    detail
        .fields
        .iter()
        .find(|(key, _)| key == "state")
        .map_or("", |(_, value)| value.as_str())
}

/// A configuration reload must replace the capacity index and issue snapshot
/// together. Pairing a new ceiling with the old snapshot is the one path that can
/// produce `uncountable`; the defensive state stays, but the TUI never keeps the
/// mismatched pair after noticing `tix.toml` changed.
#[test]
fn reloading_configuration_rebuilds_the_capacity_snapshot_with_its_conversions() {
    let tmp = tempfile::TempDir::new().unwrap();
    let dir = tmp.path().join("issues");
    fs::create_dir_all(&dir).unwrap();
    let mut config = Config {
        issues_dir: dir.clone(),
        ..test_config()
    };
    config.cycles.insert(
        "Cycle 1".into(),
        crate::config::PlanningCycle {
            description: None,
            start_date: Some("2026-01-01".into()),
            due_date: Some("2026-01-31".into()),
        },
    );
    let board = config.kanban.configurations.get_mut("default").unwrap();
    board.wip.units.insert("d".into(), 1);
    config.capacity.insert(
        "cycle-one",
        crate::capacity::config::CapacityEntry {
            cycle: Some("Cycle 1".into()),
            capacity: Some(10),
            unit: Some("d".into()),
            ..Default::default()
        },
    );
    let config_path = tmp.path().join("tix.toml");
    config.save(&config_path).unwrap();
    let config = Config::load_with_warning(&config_path, |_| {}).unwrap();

    fs::write(
        dir.join("2026-01-01-a.md"),
        "---\nnumber: 1\ntitle: \"A\"\nstatus: \"todo\"\nlabels: []\n\
         priority: \"medium\"\nestimate: \"48d\"\ncycles: [\"Cycle 1\"]\n\
         created: \"2026-01-01\"\nupdated: \"2026-01-01\"\n---\n\nBody.\n",
    )
    .unwrap();

    let mut app = App::new(&config).unwrap();
    app.refresh_capacity_view();

    let mut changed = app.store.config.clone();
    changed
        .kanban
        .configurations
        .get_mut("default")
        .unwrap()
        .wip
        .units
        .insert("h".into(), 8);
    changed.capacity.get_mut("cycle-one").unwrap().unit = Some("h".into());
    let cache = app.capacity.as_ref().unwrap();
    let (stale_pair, _) =
        App::build_capacity_view(&changed, tmp.path(), &cache.index, &cache.snapshot);
    assert_eq!(
        state(&stale_pair.details[0]),
        "uncountable",
        "the old snapshot has no `h` conversion, which is the defensive state"
    );

    changed.save(&config_path).unwrap();
    assert!(
        app.reload_config_if_changed(),
        "the changed tix.toml is adopted"
    );
    let reloaded = app
        .capacity
        .as_ref()
        .expect("the reload rebuilt the capacity cache");
    assert_eq!(
        state(&reloaded.view.details[0]),
        "configured",
        "the new ceiling and a snapshot carrying its unit are reloaded together"
    );
    assert!(
        reloaded.snapshot.targets().iter().any(|unit| unit == "h"),
        "the rebuilt issue snapshot includes the reloaded ceiling's unit"
    );
}