T25b Generative: queue and results
GenerationQueue shows each job's picture or where it stands, its prompt and its progress, with Cancel, Retry and Remove as its state allows; new rows fold in. GenerationGrid lays a run's results out in their shape: breathing while they come, the picture once done, why one failed with Retry. VariationPicker shows the chosen variation large over thumbnails and ways on from it. ABCompareView sets two to four models' answers side by side, blind until a verdict. ImageCompare points to data_display::BeforeAfter. primitives::framed and checked_ratio now serve previews and generative alike. AGENTS.md records that gpui reads a &str picture as a web address, and that grid rows overgrow tiles that keep a ratio.
This commit is contained in:
17 files changed
+1515
-215
No files matched your search
+4
-190
@@ -1,17 +1,9 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use gpui::{KeyBinding, KeyUpEvent, Keystroke, TestAppContext, VisualTestContext};
|
||||
|
||||
use gpui::{
|
||||
AppContext as _, Context, Entity, Focusable, IntoElement, KeyBinding, KeyUpEvent, Keystroke,
|
||||
ParentElement, Render, SharedString, Styled, TestAppContext, VisualTestContext, Window, div,
|
||||
px,
|
||||
};
|
||||
use crate::{forms, primitives::FocusNext, theme::Theme};
|
||||
|
||||
use crate::{
|
||||
forms::{self, TextInput},
|
||||
generative::{AspectRatioPicker, PromptEnhancer, SeedInput, StylePreset, StylePresetPicker},
|
||||
primitives::FocusNext,
|
||||
theme::Theme,
|
||||
};
|
||||
mod results;
|
||||
mod settings;
|
||||
|
||||
fn settle(cx: &mut VisualTestContext) {
|
||||
cx.run_until_parked();
|
||||
@@ -42,181 +34,3 @@ fn setup(cx: &mut TestAppContext) {
|
||||
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
|
||||
});
|
||||
}
|
||||
|
||||
/// A prompt with its enhancer, a suggestion the test sets, and what it heard.
|
||||
struct Enhancing {
|
||||
field: Entity<TextInput>,
|
||||
suggestion: Option<SharedString>,
|
||||
heard: Vec<String>,
|
||||
}
|
||||
|
||||
impl Render for Enhancing {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (asked, resolved) = (cx.entity(), cx.entity());
|
||||
let enhancer = PromptEnhancer::new("enhancer", &self.field)
|
||||
.on_enhance(move |prompt, _, cx| {
|
||||
asked.update(cx, |view, _| view.heard.push(format!("asked {prompt}")))
|
||||
})
|
||||
.on_resolve(move |taken, _, cx| {
|
||||
resolved.update(cx, |view, _| {
|
||||
view.heard.push(format!("taken {taken}"));
|
||||
view.suggestion = None;
|
||||
})
|
||||
});
|
||||
let enhancer = match &self.suggestion {
|
||||
Some(text) => enhancer.suggestion(text.clone()),
|
||||
None => enhancer,
|
||||
};
|
||||
div().w(px(420.0)).child(enhancer)
|
||||
}
|
||||
}
|
||||
|
||||
fn enhancing(cx: &mut TestAppContext) -> (Entity<Enhancing>, &mut VisualTestContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|window, cx| Enhancing {
|
||||
field: cx.new(|cx| TextInput::new(window, cx)),
|
||||
suggestion: None,
|
||||
heard: Vec::new(),
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn enhance_asks_with_the_trimmed_prompt_and_use_this_takes_the_offer(cx: &mut TestAppContext) {
|
||||
let (view, cx) = enhancing(cx);
|
||||
tab(1, cx);
|
||||
cx.simulate_input(" a cat ");
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
view.update(cx, |view, cx| {
|
||||
view.suggestion = Some("a fluffy cat asleep".into());
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
view.read_with(cx, |view, cx| {
|
||||
assert_eq!(view.heard, ["asked a cat", "taken true"]);
|
||||
assert_eq!(view.field.read(cx).text(), "a fluffy cat asleep");
|
||||
});
|
||||
assert!(field_focused(&view, cx), "the field takes focus back");
|
||||
}
|
||||
|
||||
fn field_focused(view: &Entity<Enhancing>, cx: &mut VisualTestContext) -> bool {
|
||||
cx.update(|window, cx| view.read(cx).field.focus_handle(cx).is_focused(window))
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn keep_mine_leaves_the_prompt_as_written(cx: &mut TestAppContext) {
|
||||
let (view, cx) = enhancing(cx);
|
||||
tab(1, cx);
|
||||
cx.simulate_input("a cat");
|
||||
view.update(cx, |view, cx| {
|
||||
view.suggestion = Some("a fluffy cat".into());
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
tab(3, cx);
|
||||
press("enter", cx);
|
||||
view.read_with(cx, |view, cx| {
|
||||
assert_eq!(view.heard, ["taken false"]);
|
||||
assert_eq!(view.field.read(cx).text(), "a cat");
|
||||
});
|
||||
assert!(field_focused(&view, cx), "the field takes focus back");
|
||||
}
|
||||
|
||||
/// Three presets, two ratios, a seed, and every pick heard.
|
||||
struct Picks {
|
||||
seed: Entity<TextInput>,
|
||||
style: SharedString,
|
||||
heard: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl Render for Picks {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (styled, shaped) = (self.heard.clone(), self.heard.clone());
|
||||
let preset = |key: &str| StylePreset {
|
||||
key: key.to_string().into(),
|
||||
name: key.to_string().into(),
|
||||
picture: None,
|
||||
};
|
||||
div()
|
||||
.w(px(420.0))
|
||||
.child(
|
||||
StylePresetPicker::new(
|
||||
"styles",
|
||||
[preset("a"), preset("b"), preset("c")],
|
||||
self.style.clone(),
|
||||
)
|
||||
.on_select(move |key, _, _| styled.borrow_mut().push(format!("style {key}"))),
|
||||
)
|
||||
.child(
|
||||
AspectRatioPicker::new("ratios", [(1, 1), (4, 3)], (1, 1)).on_select(
|
||||
move |ratio, _, _| {
|
||||
shaped
|
||||
.borrow_mut()
|
||||
.push(format!("ratio {}:{}", ratio.0, ratio.1))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(SeedInput::new("seed", &self.seed))
|
||||
}
|
||||
}
|
||||
|
||||
fn picks(
|
||||
cx: &mut TestAppContext,
|
||||
) -> (
|
||||
Entity<Picks>,
|
||||
Rc<RefCell<Vec<String>>>,
|
||||
&mut VisualTestContext,
|
||||
) {
|
||||
setup(cx);
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let store = heard.clone();
|
||||
let (view, cx) = cx.add_window_view(|window, cx| Picks {
|
||||
seed: cx.new(|cx| TextInput::new(window, cx)),
|
||||
style: "a".into(),
|
||||
heard: store,
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
(view, heard, cx)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn tabs_reach_each_tile_and_chip_and_enter_picks_it(cx: &mut TestAppContext) {
|
||||
let (_, heard, cx) = picks(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(3, cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
["style b", "ratio 4:3"],
|
||||
"the chosen tile stays quiet"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_seed_keeps_digits_and_the_die_rolls_one(cx: &mut TestAppContext) {
|
||||
let (view, _, cx) = picks(cx);
|
||||
tab(6, cx);
|
||||
cx.simulate_input("12a3");
|
||||
view.read_with(cx, |view, cx| {
|
||||
assert_eq!(SeedInput::read(&view.seed, cx), Some(123))
|
||||
});
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
view.read_with(cx, |view, cx| {
|
||||
let text = view.seed.read(cx).text().to_string();
|
||||
assert_ne!(text, "123", "the die rolls a new seed");
|
||||
assert_eq!(
|
||||
SeedInput::read(&view.seed, cx).map(|seed| seed.to_string()),
|
||||
Some(text)
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
use gpui::{Context, IntoElement, ParentElement, Render, Styled, TestAppContext, Window, div, px};
|
||||
|
||||
use super::{press, settle, setup, tab};
|
||||
use crate::generative::{
|
||||
ABCompareView, GenerationGrid, GenerationQueue, Job, JobState, Outcome, VariationPicker,
|
||||
Verdict,
|
||||
};
|
||||
|
||||
type Heard = Rc<RefCell<Vec<String>>>;
|
||||
|
||||
/// A queue of a running, a failed and a finished job, and the actions heard.
|
||||
struct Queue(Heard);
|
||||
|
||||
impl Render for Queue {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let job = |key: &str, state| Job {
|
||||
key: key.to_string().into(),
|
||||
prompt: "a white atrium".into(),
|
||||
state,
|
||||
picture: None,
|
||||
};
|
||||
let (canceled, retried, removed) = (self.0.clone(), self.0.clone(), self.0.clone());
|
||||
div().w(px(420.0)).child(
|
||||
GenerationQueue::new(
|
||||
"queue",
|
||||
[
|
||||
job("run", JobState::Running(0.4, Some(Duration::from_secs(12)))),
|
||||
job("fail", JobState::Failed("out of memory".into())),
|
||||
job("done", JobState::Done(Duration::from_secs(30))),
|
||||
],
|
||||
)
|
||||
.on_cancel(move |key, _, _| canceled.borrow_mut().push(format!("cancel {key}")))
|
||||
.on_retry(move |key, _, _| retried.borrow_mut().push(format!("retry {key}")))
|
||||
.on_remove(move |key, _, _| removed.borrow_mut().push(format!("remove {key}"))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn each_job_offers_what_its_state_allows(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Queue(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
for _ in 0..4 {
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
}
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
["cancel run", "retry fail", "remove fail", "remove done"]
|
||||
);
|
||||
}
|
||||
|
||||
/// A grid of a picture, a pending result and a failure, and what it heard.
|
||||
struct Grid(Heard);
|
||||
|
||||
impl Render for Grid {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (opened, retried) = (self.0.clone(), self.0.clone());
|
||||
let results = [
|
||||
Outcome::Pending(Some(0.3)),
|
||||
Outcome::Done("missing.jpg".into()),
|
||||
Outcome::Failed("blocked".into()),
|
||||
];
|
||||
div().w(px(420.0)).child(
|
||||
GenerationGrid::new("grid", results, 1.5)
|
||||
.on_open(move |ix, _, _| opened.borrow_mut().push(format!("open {ix}")))
|
||||
.on_retry(move |ix, _, _| retried.borrow_mut().push(format!("retry {ix}"))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn only_done_results_open_and_a_failure_retries(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Grid(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
["open 1", "retry 2"],
|
||||
"the pending result takes no Tab"
|
||||
);
|
||||
}
|
||||
|
||||
/// Three variations with the first chosen, and what it heard.
|
||||
struct Variations(Heard);
|
||||
|
||||
impl Render for Variations {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (chosen, varied) = (self.0.clone(), self.0.clone());
|
||||
div().w(px(420.0)).child(
|
||||
VariationPicker::new("variations", ["a.jpg", "b.jpg", "c.jpg"], 1.5, 0)
|
||||
.on_choose(move |ix, _, _| chosen.borrow_mut().push(format!("choose {ix}")))
|
||||
.on_vary(move |strong, _, _| varied.borrow_mut().push(format!("vary {strong}"))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_thumbnail_chooses_and_the_chosen_one_is_quiet(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Variations(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(2, cx);
|
||||
press("enter", cx);
|
||||
tab(2, cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(*heard.borrow(), ["choose 2", "vary true"]);
|
||||
}
|
||||
|
||||
/// A blind comparison of two answers, deciding what it hears.
|
||||
struct Compared {
|
||||
verdict: Option<Verdict>,
|
||||
heard: Heard,
|
||||
}
|
||||
|
||||
impl Render for Compared {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity();
|
||||
let compare = ABCompareView::new("compare")
|
||||
.side("Orchid", "one answer")
|
||||
.side("Juniper", "another")
|
||||
.blind()
|
||||
.on_verdict(move |verdict, _, cx| {
|
||||
view.update(cx, |compared, cx| {
|
||||
compared.heard.borrow_mut().push(format!("{verdict:?}"));
|
||||
compared.verdict = Some(verdict);
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
let compare = match self.verdict {
|
||||
Some(verdict) => compare.verdict(verdict),
|
||||
None => compare,
|
||||
};
|
||||
div().w(px(420.0)).child(compare)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_verdict_is_given_once(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Compared {
|
||||
verdict: None,
|
||||
heard: store,
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab(2, cx);
|
||||
press("enter", cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
["Side(1)"],
|
||||
"the buttons leave with the verdict"
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
AppContext as _, Context, Entity, Focusable, IntoElement, ParentElement, Render, SharedString,
|
||||
Styled, TestAppContext, VisualTestContext, Window, div, px,
|
||||
};
|
||||
|
||||
use super::{press, settle, setup, tab};
|
||||
use crate::{
|
||||
forms::TextInput,
|
||||
generative::{AspectRatioPicker, PromptEnhancer, SeedInput, StylePreset, StylePresetPicker},
|
||||
};
|
||||
|
||||
/// A prompt with its enhancer, a suggestion the test sets, and what it heard.
|
||||
struct Enhancing {
|
||||
field: Entity<TextInput>,
|
||||
suggestion: Option<SharedString>,
|
||||
heard: Vec<String>,
|
||||
}
|
||||
|
||||
impl Render for Enhancing {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (asked, resolved) = (cx.entity(), cx.entity());
|
||||
let enhancer = PromptEnhancer::new("enhancer", &self.field)
|
||||
.on_enhance(move |prompt, _, cx| {
|
||||
asked.update(cx, |view, _| view.heard.push(format!("asked {prompt}")))
|
||||
})
|
||||
.on_resolve(move |taken, _, cx| {
|
||||
resolved.update(cx, |view, _| {
|
||||
view.heard.push(format!("taken {taken}"));
|
||||
view.suggestion = None;
|
||||
})
|
||||
});
|
||||
let enhancer = match &self.suggestion {
|
||||
Some(text) => enhancer.suggestion(text.clone()),
|
||||
None => enhancer,
|
||||
};
|
||||
div().w(px(420.0)).child(enhancer)
|
||||
}
|
||||
}
|
||||
|
||||
fn enhancing(cx: &mut TestAppContext) -> (Entity<Enhancing>, &mut VisualTestContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|window, cx| Enhancing {
|
||||
field: cx.new(|cx| TextInput::new(window, cx)),
|
||||
suggestion: None,
|
||||
heard: Vec::new(),
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn enhance_asks_with_the_trimmed_prompt_and_use_this_takes_the_offer(cx: &mut TestAppContext) {
|
||||
let (view, cx) = enhancing(cx);
|
||||
tab(1, cx);
|
||||
cx.simulate_input(" a cat ");
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
view.update(cx, |view, cx| {
|
||||
view.suggestion = Some("a fluffy cat asleep".into());
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
view.read_with(cx, |view, cx| {
|
||||
assert_eq!(view.heard, ["asked a cat", "taken true"]);
|
||||
assert_eq!(view.field.read(cx).text(), "a fluffy cat asleep");
|
||||
});
|
||||
assert!(field_focused(&view, cx), "the field takes focus back");
|
||||
}
|
||||
|
||||
fn field_focused(view: &Entity<Enhancing>, cx: &mut VisualTestContext) -> bool {
|
||||
cx.update(|window, cx| view.read(cx).field.focus_handle(cx).is_focused(window))
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn keep_mine_leaves_the_prompt_as_written(cx: &mut TestAppContext) {
|
||||
let (view, cx) = enhancing(cx);
|
||||
tab(1, cx);
|
||||
cx.simulate_input("a cat");
|
||||
view.update(cx, |view, cx| {
|
||||
view.suggestion = Some("a fluffy cat".into());
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
tab(3, cx);
|
||||
press("enter", cx);
|
||||
view.read_with(cx, |view, cx| {
|
||||
assert_eq!(view.heard, ["taken false"]);
|
||||
assert_eq!(view.field.read(cx).text(), "a cat");
|
||||
});
|
||||
assert!(field_focused(&view, cx), "the field takes focus back");
|
||||
}
|
||||
|
||||
/// Three presets, two ratios, a seed, and every pick heard.
|
||||
struct Picks {
|
||||
seed: Entity<TextInput>,
|
||||
style: SharedString,
|
||||
heard: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl Render for Picks {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (styled, shaped) = (self.heard.clone(), self.heard.clone());
|
||||
let preset = |key: &str| StylePreset {
|
||||
key: key.to_string().into(),
|
||||
name: key.to_string().into(),
|
||||
picture: None,
|
||||
};
|
||||
div()
|
||||
.w(px(420.0))
|
||||
.child(
|
||||
StylePresetPicker::new(
|
||||
"styles",
|
||||
[preset("a"), preset("b"), preset("c")],
|
||||
self.style.clone(),
|
||||
)
|
||||
.on_select(move |key, _, _| styled.borrow_mut().push(format!("style {key}"))),
|
||||
)
|
||||
.child(
|
||||
AspectRatioPicker::new("ratios", [(1, 1), (4, 3)], (1, 1)).on_select(
|
||||
move |ratio, _, _| {
|
||||
shaped
|
||||
.borrow_mut()
|
||||
.push(format!("ratio {}:{}", ratio.0, ratio.1))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(SeedInput::new("seed", &self.seed))
|
||||
}
|
||||
}
|
||||
|
||||
fn picks(
|
||||
cx: &mut TestAppContext,
|
||||
) -> (
|
||||
Entity<Picks>,
|
||||
Rc<RefCell<Vec<String>>>,
|
||||
&mut VisualTestContext,
|
||||
) {
|
||||
setup(cx);
|
||||
let heard = Rc::new(RefCell::new(Vec::new()));
|
||||
let store = heard.clone();
|
||||
let (view, cx) = cx.add_window_view(|window, cx| Picks {
|
||||
seed: cx.new(|cx| TextInput::new(window, cx)),
|
||||
style: "a".into(),
|
||||
heard: store,
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
(view, heard, cx)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn tabs_reach_each_tile_and_chip_and_enter_picks_it(cx: &mut TestAppContext) {
|
||||
let (_, heard, cx) = picks(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(3, cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
["style b", "ratio 4:3"],
|
||||
"the chosen tile stays quiet"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_seed_keeps_digits_and_the_die_rolls_one(cx: &mut TestAppContext) {
|
||||
let (view, _, cx) = picks(cx);
|
||||
tab(6, cx);
|
||||
cx.simulate_input("12a3");
|
||||
view.read_with(cx, |view, cx| {
|
||||
assert_eq!(SeedInput::read(&view.seed, cx), Some(123))
|
||||
});
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
view.read_with(cx, |view, cx| {
|
||||
let text = view.seed.read(cx).text().to_string();
|
||||
assert_ne!(text, "123", "the die rolls a new seed");
|
||||
assert_eq!(
|
||||
SeedInput::read(&view.seed, cx).map(|seed| seed.to_string()),
|
||||
Some(text)
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user