Files
Ely-GPUI-Components/src/finance/tests.rs
T

177 lines
5.3 KiB
Rust

use std::rc::Rc;
use gpui::{
AppContext as _, Context, Entity, IntoElement, ParentElement, Render, Styled, TestAppContext,
VisualTestContext, Window, div, px,
};
use jiff::Timestamp;
use crate::finance::{
CandlestickChart, ChartSync, CurrencyConverter, DepthChart, Leg, MarketHeatmap, PayoffDiagram,
PointFigureChart, RenkoChart, candles::Candle, stage::Visible,
};
use crate::{layout::tests::narrow_width, theme::Theme};
fn candles(count: usize) -> Rc<Vec<Candle>> {
Rc::new(
(0..count)
.map(|ix| {
let time = Timestamp::from_second(ix as i64 * 86_400).expect("a day");
Candle::new(time, (10.0, 11.0, 9.0, 10.0 + ix as f64 * 0.01), 100.0)
})
.collect(),
)
}
struct Pair {
first: Rc<Vec<Candle>>,
second: Rc<Vec<Candle>>,
sync: Entity<ChartSync>,
}
impl Render for Pair {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.w(px(800.))
.child(CandlestickChart::new("first", self.first.clone()).sync(&self.sync))
.child(CandlestickChart::new("second", self.second.clone()).sync(&self.sync))
}
}
/// Two synced charts of 40 candles, their shared window held on the newest 20.
fn held(cx: &mut TestAppContext) -> (Entity<Pair>, Entity<ChartSync>, &mut VisualTestContext) {
cx.update(Theme::init);
let sync = cx.new(|_| ChartSync::default());
let shared = sync.clone();
let (view, cx) = cx.add_window_view(|_, _| Pair {
first: candles(40),
second: candles(40),
sync: shared,
});
cx.run_until_parked();
sync.update(cx, |sync, cx| {
sync.visible = Some(Visible {
start: 20.0,
count: 20.0,
});
cx.notify();
});
view.update(cx, |_, cx| cx.notify());
cx.run_until_parked();
(view, sync, cx)
}
fn start(sync: &Entity<ChartSync>, cx: &mut VisualTestContext) -> f64 {
sync.read_with(cx, |sync, _| sync.visible.expect("held").start)
}
#[gpui::test]
fn synced_charts_move_their_shared_window_once_per_append(cx: &mut TestAppContext) {
let (view, sync, cx) = held(cx);
view.update(cx, |pair, cx| {
pair.first = candles(45);
pair.second = candles(45);
cx.notify();
});
cx.run_until_parked();
assert_eq!(
start(&sync, cx),
25.0,
"five new candles move the shared window five, not ten"
);
}
#[gpui::test]
fn a_lagging_chart_leaves_the_shared_window_alone(cx: &mut TestAppContext) {
let (view, sync, cx) = held(cx);
view.update(cx, |pair, cx| {
pair.second = candles(45);
cx.notify();
});
cx.run_until_parked();
for _ in 0..2 {
view.update(cx, |_, cx| cx.notify());
cx.run_until_parked();
}
assert_eq!(
(start(&sync, cx), sync.read_with(cx, |sync, _| sync.seen)),
(25.0, 45),
"redraws while the first chart lags move nothing"
);
view.update(cx, |pair, cx| {
pair.first = candles(45);
cx.notify();
});
cx.run_until_parked();
assert_eq!(
start(&sync, cx),
25.0,
"the lagging chart's append is already shown"
);
}
#[gpui::test]
fn every_chart_fills_a_column_its_block_measures_by_content(cx: &mut TestAppContext) {
cx.update(Theme::init);
let leg = Leg {
call: true,
strike: 100.0,
quantity: 1.0,
premium: 2.0,
};
let widths = [
narrow_width(cx, "chart-root", |_, _| {
DepthChart::new("depth", [(99.0, 2.0)], [(101.0, 3.0)]).into_any_element()
}),
narrow_width(cx, "chart-root", |_, _| {
MarketHeatmap::new("heatmap")
.tile("A", 1.0, 0.5)
.into_any_element()
}),
narrow_width(cx, "chart-root", |_, _| {
CandlestickChart::new("candles", candles(30)).into_any_element()
}),
narrow_width(cx, "chart-root", move |_, _| {
PayoffDiagram::new("payoff", [leg], (80.0, 120.0), 100.0).into_any_element()
}),
narrow_width(cx, "chart-root", |_, _| {
RenkoChart::new("renko", candles(30), 0.1).into_any_element()
}),
narrow_width(cx, "chart-root", |_, _| {
PointFigureChart::new("figure", candles(30), 0.1, 3).into_any_element()
}),
];
assert_eq!(
widths,
[px(240.0); 6],
"each chart spans the card inside its padding"
);
}
/// A converter squeezed beside a card in a 280px row.
struct Squeezed;
impl Render for Squeezed {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let converter = CurrencyConverter::new("converter", 1000.0, ("USD", "EUR"), 0.9174);
div().w(px(280.)).child(
div()
.flex()
.gap_8()
.child(div().w(px(460.)).child(converter))
.child(div().flex_1().child(div().w(px(260.)).h(px(100.)))),
)
}
}
#[gpui::test]
fn a_squeezed_converter_keeps_room_for_its_currencies(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (_, cx) = cx.add_window_view(|_, _| Squeezed);
cx.run_until_parked();
let text = cx
.debug_bounds("input-text")
.expect("the second currency's text");
assert_eq!(text.size.width, px(26.), "the frame's inset for its name");
}