Files
galaxy/crates/onboarding/src/model_tests.rs
T

329 lines
13 KiB
Rust

use ai::LLMId;
use galaxy_core::features::FeatureFlag;
use galaxy_core::telemetry::testing::MockTelemetryContextProvider;
use galaxyui_core::{App, ModelHandle};
use crate::model::{
AiSetupChoice, NoAiConfirmationSource, OnboardingAuthState, OnboardingStateModel,
OnboardingStep, SelectedSettings,
};
use crate::OnboardingIntention;
fn add_test_model(app: &mut App) -> ModelHandle<OnboardingStateModel> {
app.update(MockTelemetryContextProvider::register);
app.add_model(|_| {
OnboardingStateModel::new(
Vec::new(),
LLMId::from("auto"),
false,
true,
OnboardingAuthState::FreeUser,
)
})
}
fn step(app: &App, model: &ModelHandle<OnboardingStateModel>) -> OnboardingStep {
model.read(app, |model, _| model.step())
}
#[test]
fn agent_path_routes_through_ai_setup() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
// Default intention is agent-driven development.
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Intention);
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::AiSetup);
// The default AI setup choice is the Warp agent.
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Agent);
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::AiAccess);
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Customize);
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::ThemePicker);
// Back navigation mirrors the forward path.
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Customize);
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::AiAccess);
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Agent);
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::AiSetup);
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Intention);
});
}
#[test]
fn third_party_choice_routes_to_third_party_slide() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| {
model.next(ctx); // Intro → Intention
model.next(ctx); // Intention → AiSetup
model.set_ai_setup_choice(AiSetupChoice::ThirdParty, ctx);
model.next(ctx); // AiSetup → ThirdParty
});
assert_eq!(step(&app, &model), OnboardingStep::ThirdParty);
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Customize);
// Back from Customize returns to the chosen AI-setup slide.
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::ThirdParty);
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::AiSetup);
});
}
#[test]
fn confirm_no_ai_switches_to_terminal_path() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| {
model.next(ctx); // Intro → Intention
model.set_intention_terminal(ctx);
model.request_no_ai_confirmation(NoAiConfirmationSource::Intention, ctx);
});
// The confirmation modal is shown without leaving the intention slide yet.
assert_eq!(step(&app, &model), OnboardingStep::Intention);
model.read(&app, |model, _| {
assert_eq!(
model.no_ai_confirmation(),
Some(NoAiConfirmationSource::Intention)
);
});
// Confirming "I don't want AI" lands on the terminal path, never a dead end.
model.update(&mut app, |model, ctx| model.confirm_no_ai(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Customize);
model.read(&app, |model, _| {
assert_eq!(model.no_ai_confirmation(), None);
assert_eq!(*model.intention(), OnboardingIntention::Terminal);
assert!(!model.settings().is_ai_enabled());
});
// The terminal path continues to completion, skipping the third-party slide.
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), OnboardingStep::ThemePicker);
});
}
#[test]
fn confirm_no_ai_from_intention_then_back_returns_to_intention() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| {
model.next(ctx); // Intro → Intention
model.set_intention_terminal(ctx);
model.request_no_ai_confirmation(NoAiConfirmationSource::Intention, ctx);
});
// "Just use the terminal" + Next does not advance until the user confirms.
assert_eq!(step(&app, &model), OnboardingStep::Intention);
model.update(&mut app, |model, ctx| model.confirm_no_ai(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Customize);
// Back from Customize goes to the intention fork, not the AI-setup slide.
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Intention);
});
}
#[test]
fn cancel_no_ai_from_intention_routes_to_ai_setup() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| {
model.next(ctx); // Intro → Intention
model.set_intention_terminal(ctx);
model.request_no_ai_confirmation(NoAiConfirmationSource::Intention, ctx);
});
// "Give me AI features" switches onto the AI path and opens the AI-setup slide.
model.update(&mut app, |model, ctx| model.cancel_no_ai(ctx));
assert_eq!(step(&app, &model), OnboardingStep::AiSetup);
model.read(&app, |model, _| {
assert_eq!(model.no_ai_confirmation(), None);
assert_eq!(
*model.intention(),
OnboardingIntention::AgentDrivenDevelopment
);
});
});
}
#[test]
fn dismiss_no_ai_closes_without_changing_path() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| {
model.next(ctx); // Intro → Intention
model.set_intention_terminal(ctx);
model.request_no_ai_confirmation(NoAiConfirmationSource::Intention, ctx);
model.dismiss_no_ai(ctx);
});
assert_eq!(step(&app, &model), OnboardingStep::Intention);
model.read(&app, |model, _| {
assert_eq!(model.no_ai_confirmation(), None);
assert_eq!(*model.intention(), OnboardingIntention::Terminal);
});
});
}
#[test]
fn terminal_settings_disable_ai() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| model.set_intention_terminal(ctx));
model.read(&app, |model, _| {
assert!(matches!(
model.settings(),
SelectedSettings::Terminal { .. }
));
assert!(!model.settings().is_ai_enabled());
});
});
}
#[test]
fn agent_intent_keeps_ai_enabled_for_any_setup_choice() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
// Default agent intention + "Use Warp Agent" enables AI.
model.read(&app, |model, _| assert!(model.settings().is_ai_enabled()));
// "Use third party agents" still keeps AI enabled: agent intent always
// means the user wants AI, even when bringing their own agents.
model.update(&mut app, |model, ctx| {
model.set_ai_setup_choice(AiSetupChoice::ThirdParty, ctx)
});
model.read(&app, |model, _| assert!(model.settings().is_ai_enabled()));
// Switching back to Warp Agent also keeps AI enabled.
model.update(&mut app, |model, ctx| {
model.set_ai_setup_choice(AiSetupChoice::WarpAgent, ctx)
});
model.read(&app, |model, _| assert!(model.settings().is_ai_enabled()));
});
}
#[test]
fn terminal_path_skips_third_party() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| model.set_intention_terminal(ctx));
// Terminal goes Intention → Customize → ThemePicker; the "Customize third
// party agents" slide is only for the agent → third-party choice.
for expected in [
OnboardingStep::Intention,
OnboardingStep::Customize,
OnboardingStep::ThemePicker,
] {
model.update(&mut app, |model, ctx| model.next(ctx));
assert_eq!(step(&app, &model), expected);
}
// Back navigation mirrors the forward path, also skipping third-party.
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Customize);
model.update(&mut app, |model, ctx| model.back(ctx));
assert_eq!(step(&app, &model), OnboardingStep::Intention);
});
}
#[test]
fn progress_reports_v3_positions_for_agent_path() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
// Warp Agent path: Intention → AiSetup → Agent → AiAccess → Customize → ThemePicker.
let cases = [
(OnboardingStep::Intention, (0, 6)),
(OnboardingStep::AiSetup, (1, 6)),
(OnboardingStep::Agent, (2, 6)),
(OnboardingStep::AiAccess, (3, 6)),
(OnboardingStep::Customize, (4, 6)),
(OnboardingStep::ThemePicker, (5, 6)),
];
for (target, expected) in cases {
model.update(&mut app, |model, ctx| model.set_step(target, ctx));
let progress = model.read(&app, |model, _| model.progress());
assert_eq!(progress, expected, "unexpected dots for {target:?}");
}
});
}
#[test]
fn progress_reports_v3_positions_for_third_party_path() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| {
model.set_ai_setup_choice(AiSetupChoice::ThirdParty, ctx)
});
// Third-party path has no "Choose how to access AI" step, so it is one
// dot shorter than the Warp Agent path.
let cases = [
(OnboardingStep::Intention, (0, 5)),
(OnboardingStep::AiSetup, (1, 5)),
(OnboardingStep::ThirdParty, (2, 5)),
(OnboardingStep::Customize, (3, 5)),
(OnboardingStep::ThemePicker, (4, 5)),
];
for (target, expected) in cases {
model.update(&mut app, |model, ctx| model.set_step(target, ctx));
let progress = model.read(&app, |model, _| model.progress());
assert_eq!(progress, expected, "unexpected dots for {target:?}");
}
});
}
#[test]
fn progress_reports_terminal_path_uses_three_dot_variant() {
let _flag = FeatureFlag::OpenWarpNewSettingsModes.override_enabled(true);
App::test((), |mut app| async move {
let model = add_test_model(&mut app);
model.update(&mut app, |model, ctx| model.set_intention_terminal(ctx));
let cases = [
(OnboardingStep::Intention, (0, 3)),
(OnboardingStep::Customize, (1, 3)),
(OnboardingStep::ThemePicker, (2, 3)),
];
for (target, expected) in cases {
model.update(&mut app, |model, ctx| model.set_step(target, ctx));
let progress = model.read(&app, |model, _| model.progress());
assert_eq!(progress, expected, "unexpected dots for {target:?}");
}
});
}