alphai-tui 0.30.1

Terminal stock dashboard: live quotes and charts plus AI-scored financial news and SEC Form 4 insider activity from AlphAI
use super::*;
use crate::domain::PriceFeed;

#[test]
fn quote_and_bar_keep_their_own_price_feed_and_clock() {
    let mut app = fake_app();
    app.view_idx = ui::view_index(ui::ViewId::Chart);
    app.source_delay = None;
    app.sessions = Sessions::Extended;
    let ts = chrono::DateTime::parse_from_rfc3339("2026-09-30T14:00:00Z")
        .unwrap()
        .timestamp();
    let data = app.data.get_mut("AAPL").unwrap();
    data.quote.price = 220.25;
    data.quote.currency = Some("EUR".into());
    data.quote.timing.regular = Some(ts + 1800);
    data.quote.timing.regular_feed = PriceFeed::Iex;
    for (i, bar) in data.candles.iter_mut().enumerate() {
        bar.ts = ts - (29 - i) as i64 * 300;
        bar.feed = PriceFeed::DelayedSip;
    }
    let screen = render_sized(&mut app, 160, 40);
    let rail = screen.lines().nth(1).unwrap();
    for part in ["quote 220.25 EUR", "IEX", "30 Sep 10:30 ET"] {
        assert!(rail.contains(part), "{rail}");
    }
    let bar = screen.lines().find(|l| l.contains("Last bar")).unwrap();
    for part in ["214.50", "SIP · delayed 15m", "30 Sep 10:00 ET"] {
        assert!(bar.contains(part), "{bar}");
    }
    assert!(!screen.lines().nth(2).unwrap().contains("220.25"));
    app.show_rail = false;
    let screen = render_sized(&mut app, 160, 40);
    let title = screen.lines().nth(1).unwrap();
    assert!(
        title.contains("quote 220.25 EUR") && title.contains("+20.25"),
        "{title}"
    );
}

#[test]
fn news_keeps_the_selected_company_even_after_four_other_analyses() {
    let mut app = fake_app();
    app.view_idx = ui::view_index(ui::ViewId::News);
    let mut a = article("Sector news", "AAPL", 9, "positive");
    let insights = a.enrichment.ai_trading_insights.as_mut().unwrap();
    let mut selected = insights.ticker_analysis[0].clone();
    selected.ticker = "aapl".into();
    selected.impact_analysis.as_mut().unwrap().summary =
        Some("Selected company explanation".into());
    insights.ticker_analysis = (0..4)
        .map(|i| {
            let mut t = selected.clone();
            t.ticker = format!("OTHER{i}");
            t.impact_analysis.as_mut().unwrap().summary = Some("Other company explanation".into());
            t
        })
        .chain(std::iter::once(selected.clone()))
        .collect();
    app.feeds
        .insert("AAPL".into(), FeedBundle::new(vec![a], None, None));
    let screen = render_sized(&mut app, 160, 45);
    let selected = screen.find("Selected company explanation").expect(&screen);
    let summary = screen.find("Summary of Sector news").expect(&screen);
    let other = screen.find("Other company explanation").expect(&screen);
    assert!(selected < summary && summary < other, "{screen}");
    assert_eq!(screen.matches("Selected company explanation").count(), 1);
}

#[test]
fn a_market_filing_names_its_company_before_the_trade_and_ai_analysis() {
    let mut a = article("Officer sold stock", "AVGO", 8, "negative");
    a.insider = Some(alphai::InsiderEvent {
        side: Some("sell".into()),
        total_value_usd: Some("250000000".into()),
        insider_name: "EXAMPLE OFFICER".into(),
        is_10b5_1: true,
        ..Default::default()
    });
    let mut terminal = Terminal::new(TestBackend::new(80, 24)).unwrap();
    terminal
        .draw(|f| {
            ui::article::render_pane_with(
                f,
                f.area(),
                Some(&a),
                "AAPL",
                None,
                Some("stake -1.9% · 16 tranches".into()),
                "",
                &mut 0,
                &Theme::default(),
            )
        })
        .unwrap();
    let buf = terminal.backend().buffer();
    let text: String = (0..24)
        .map(|y| (0..80).map(|x| buf[(x, y)].symbol()).collect::<String>() + "\n")
        .collect();
    assert!(
        text.lines()
            .nth(1)
            .unwrap()
            .contains("AVGO · EXAMPLE OFFICER"),
        "{text}"
    );
    let trade = text.find("SELL $250.0M · 10b5-1 plan").expect(&text);
    let stake = text.find("stake -1.9% · 16 tranches").expect(&text);
    let ai = text.find("AVGO · AI impact").expect(&text);
    assert!(trade < stake && stake < ai);
    assert!(
        !text.contains("AAPL"),
        "the watchlist ticker leaked into another company's filing"
    );
}