329 lines
13 KiB
Rust
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:?}");
|
|
}
|
|
});
|
|
}
|