Account · Sign in: log in, sign up, providers, a link by email, a second factor
LoginForm takes an email and a password, keeps one signed in on a box, and signs in on Enter once the address reads and a password is typed; a failed try says why above Sign in. SignupForm ticks a new password's rules as they hold and waits for all of them and the terms. OAuthButtons offer each provider, one spinning while the rest rest. MagicLinkForm and ForgotPassword send a link to an address and, once sent, say where it went and offer it again. TwoFactorInput takes a six-digit code at once and, after one fails, clears the boxes through forms::PinInput's new attempt, keeping its focus. The Account chapter splits into sign-in, profile and access, and billing and team.
This commit is contained in:
1 parent
69befddeb3
commit
c9e3400a71
15 files changed
+1455
-10
No files matched your search
@@ -0,0 +1,85 @@
|
||||
use gpui::{
|
||||
AnyElement, App, Context, Entity, IntoElement, KeyBinding, ParentElement, Render, Styled,
|
||||
TestAppContext, VisualTestContext, Window, div, px,
|
||||
};
|
||||
|
||||
use crate::{forms, primitives::FocusNext, theme::Theme};
|
||||
|
||||
pub(super) type Part = fn(&Bench, Entity<Bench>) -> AnyElement;
|
||||
|
||||
/// A view that shows one account part, keeps what it heard, and holds what the owner decides: whether a link went, and how many codes failed.
|
||||
pub(super) struct Bench {
|
||||
part: Part,
|
||||
said: Vec<String>,
|
||||
pub(super) sent: bool,
|
||||
pub(super) failed: usize,
|
||||
}
|
||||
|
||||
impl Render for Bench {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().w(px(360.0)).child((self.part)(self, cx.entity()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bench(
|
||||
part: Part,
|
||||
cx: &mut TestAppContext,
|
||||
) -> (Entity<Bench>, &mut VisualTestContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
Theme::update(cx, |theme| theme.reduced_motion = true);
|
||||
forms::bind_keys(cx);
|
||||
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
|
||||
});
|
||||
let (host, cx) = cx.add_window_view(|_, _| Bench {
|
||||
part,
|
||||
said: Vec::new(),
|
||||
sent: false,
|
||||
failed: 0,
|
||||
});
|
||||
settle(cx);
|
||||
(host, cx)
|
||||
}
|
||||
|
||||
pub(super) fn settle(cx: &mut VisualTestContext) {
|
||||
for _ in 0..3 {
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.refresh());
|
||||
}
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
pub(super) fn say(owner: &Entity<Bench>, words: String, cx: &mut App) {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.said.push(words);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn said(host: &Entity<Bench>, cx: &mut VisualTestContext) -> Vec<String> {
|
||||
host.read_with(cx, |bench, _| bench.said.clone())
|
||||
}
|
||||
|
||||
/// Focus on the `stops`th Tab stop from the top.
|
||||
pub(super) fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
pub(super) fn tap(key: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_keystrokes(key);
|
||||
cx.simulate_event(gpui::KeyUpEvent {
|
||||
keystroke: gpui::Keystroke::parse(key).expect("a key"),
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
pub(super) fn write(text: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_input(text);
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
mod sign_in;
|
||||
@@ -0,0 +1,192 @@
|
||||
use gpui::{AnyElement, Entity, IntoElement, TestAppContext};
|
||||
|
||||
use super::{Bench, bench, said, say, settle, tab, tap, write};
|
||||
use crate::account::{
|
||||
ForgotPassword, LoginForm, MagicLinkForm, OAuthButtons, SignupForm, TwoFactorInput,
|
||||
};
|
||||
|
||||
fn login(_: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
LoginForm::new("login")
|
||||
.on_submit(move |login, _, cx| {
|
||||
say(
|
||||
&owner,
|
||||
format!("{} {} {}", login.email, login.password, login.remember),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_forgot(|_, _| {})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: email, password, its eye, the box, the forgotten password, Sign in.
|
||||
#[gpui::test]
|
||||
fn sign_in_waits_for_an_address_and_a_password_and_enter_sends(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(login, cx);
|
||||
tab(1, cx);
|
||||
write("ada@example", cx);
|
||||
tab(2, cx);
|
||||
write("secret", cx);
|
||||
tap("enter", cx);
|
||||
assert!(
|
||||
said(&host, cx).is_empty(),
|
||||
"an address without a dot is no address"
|
||||
);
|
||||
tab(1, cx);
|
||||
write(".com", cx);
|
||||
tab(4, cx);
|
||||
tap("space", cx);
|
||||
tab(2, cx);
|
||||
tap("enter", cx);
|
||||
assert_eq!(said(&host, cx), ["ada@example.com secret true"]);
|
||||
}
|
||||
|
||||
fn signup(_: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
SignupForm::new("signup")
|
||||
.terms("I agree to the terms")
|
||||
.on_submit(move |signup, _, cx| {
|
||||
say(
|
||||
&owner,
|
||||
format!("{} {} {}", signup.name, signup.email, signup.password),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: name, email, password, its eye, the terms, then Create account once it holds.
|
||||
#[gpui::test]
|
||||
fn sign_up_waits_for_every_rule_and_the_terms(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(signup, cx);
|
||||
tab(1, cx);
|
||||
write("Ada", cx);
|
||||
tab(2, cx);
|
||||
write("ada@example.com", cx);
|
||||
tab(3, cx);
|
||||
write("abcdefg1", cx);
|
||||
tap("enter", cx);
|
||||
assert!(said(&host, cx).is_empty(), "the terms are not yet accepted");
|
||||
tab(5, cx);
|
||||
tap("space", cx);
|
||||
tab(3, cx);
|
||||
tap("backspace", cx);
|
||||
tap("enter", cx);
|
||||
assert!(
|
||||
said(&host, cx).is_empty(),
|
||||
"seven characters and no number meet no rule but one"
|
||||
);
|
||||
write("1", cx);
|
||||
tap("enter", cx);
|
||||
assert_eq!(said(&host, cx), ["Ada ada@example.com abcdefg1"]);
|
||||
}
|
||||
|
||||
fn providers(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let buttons = OAuthButtons::new("oauth", [("github", "GitHub"), ("google", "Google")])
|
||||
.on_pick(move |key, _, cx| say(&owner, key.to_string(), cx));
|
||||
match bench.sent {
|
||||
true => buttons.busy("github").into_any_element(),
|
||||
false => buttons.into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops: GitHub, Google; while GitHub is on its way, neither.
|
||||
#[gpui::test]
|
||||
fn a_provider_hands_its_key_and_rests_while_one_is_on_its_way(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(providers, cx);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["google"]);
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.sent = true;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["google"],
|
||||
"no provider takes a press while one is on its way"
|
||||
);
|
||||
}
|
||||
|
||||
fn magic(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let other = owner.clone();
|
||||
MagicLinkForm::new("magic")
|
||||
.sent(bench.sent)
|
||||
.on_send(move |address, _, cx| {
|
||||
say(&owner, format!("send {address}"), cx);
|
||||
owner.update(cx, |bench, _| bench.sent = true);
|
||||
})
|
||||
.on_other(move |_, cx| {
|
||||
other.update(cx, |bench, cx| {
|
||||
bench.sent = false;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: email, then the link button; once sent, Send again and Use another email.
|
||||
#[gpui::test]
|
||||
fn a_link_goes_to_the_address_and_again_on_request(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(magic, cx);
|
||||
tab(1, cx);
|
||||
write("ada@example.com", cx);
|
||||
tap("enter", cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["send ada@example.com", "send ada@example.com"]
|
||||
);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
assert!(
|
||||
!host.read_with(cx, |bench, _| bench.sent),
|
||||
"Use another email goes back to the address"
|
||||
);
|
||||
}
|
||||
|
||||
fn forgot(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let back = owner.clone();
|
||||
ForgotPassword::new("forgot")
|
||||
.sent(bench.sent)
|
||||
.on_send(move |address, _, cx| say(&owner, format!("reset {address}"), cx))
|
||||
.on_back(move |_, cx| say(&back, "back".into(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: email, then Back to sign in while the send rests.
|
||||
#[gpui::test]
|
||||
fn a_reset_waits_for_an_address_and_offers_the_way_back(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(forgot, cx);
|
||||
tab(2, cx);
|
||||
tap("enter", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["back"],
|
||||
"with no address the send rests, so the second stop is the way back"
|
||||
);
|
||||
}
|
||||
|
||||
fn factor(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
TwoFactorInput::new("factor")
|
||||
.failed(bench.failed)
|
||||
.on_code(move |code, _, cx| say(&owner, code.to_string(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: the code.
|
||||
#[gpui::test]
|
||||
fn a_failed_code_clears_the_boxes_for_the_next(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(factor, cx);
|
||||
tab(1, cx);
|
||||
write("123456", cx);
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.failed = 1;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
write("654321", cx);
|
||||
assert_eq!(said(&host, cx), ["123456", "654321"]);
|
||||
}
|
||||
Reference in new issue
Block a user