Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
theme-studio = { max-threads = 1 }

[[profile.default.overrides]]
filter = 'test(gpui_shell::settings_pane::theme_studio_tests::) or test(ctrl_wheel_font_zoom_toggle_gates_zoom_and_terminal_scroll) or test(ctrl_wheel_font_zoom_setting_is_searchable_and_has_a_visible_switch) or test(environment_refresh_switch_is_searchable_and_persists) or test(pasted_proxy_scheme_updates_the_visible_protocol_and_saved_url) or test(capsule_uses_inset_thumb_full_hit_targets_and_keyboard_selection) or test(long_segments_use_a_real_dropdown_without_losing_preference_updates) or test(font_size_click_input_commits_cancels_and_bounds_values) or test(cjk_dropdown_selection_preserves_the_english_font_chain) or test(review_regression_font_fields_align_and_dropdown_toggles_with_search)'
filter = 'test(gpui_shell::settings_pane::theme_studio_tests::) or test(ctrl_wheel_font_zoom_toggle_gates_zoom_and_terminal_scroll) or test(ctrl_wheel_font_zoom_setting_is_searchable_and_has_a_visible_switch) or test(environment_refresh_switch_is_searchable_and_persists) or test(pasted_proxy_scheme_updates_the_visible_protocol_and_saved_url) or test(capsule_uses_inset_thumb_full_hit_targets_and_keyboard_selection) or test(long_segments_use_a_real_dropdown_without_losing_preference_updates) or test(font_size_click_input_commits_cancels_and_bounds_values) or test(cjk_dropdown_selection_preserves_the_english_font_chain) or test(review_regression_font_fields_align_and_dropdown_toggles_with_search) or test(=gpui_shell::workspace::keyboard_bindings::tests::dispatch::hybrid_completion_uses_window_tab_dispatch_in_both_workspace_layouts)'
test-group = 'theme-studio'

# 三模式夹具包含九个真实 Git/窗口场景;原生进程的墙钟期限不能用并发 UI 负载测试。
Expand Down
35 changes: 26 additions & 9 deletions nebula_app/src/gpui_shell/terminal/view/startup_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,31 @@ impl Render for Surface {
}
}

impl TerminalView {
// 工作区级按键回归复用同一个终端夹具,避免另造一套输入/会话状态。
pub(crate) fn install_completion_test_session(&mut self) -> Receiver<Msg> {
let (session, receiver) = session::test_session();
self.session = Some(session);
self.error = None;
self.exited = None;
self.exec_context = None;
self.suggest.suggest_env = crate::display::SuggestEnv::Wsl { distro: "Debian".into() };
receiver
}

pub(crate) fn completion_test_state(
&self,
) -> (String, bool, usize, bool, crate::display::CompletionStyle) {
(
self.suggest.screen_line.clone(),
self.suggest.completion_popup_requested,
self.suggest.completion_items.len(),
self.completion_popup_geometry().is_some(),
self.completion_style,
)
}
}

pub(super) fn open(
cx: &mut TestAppContext,
) -> (Entity<TerminalView>, &mut VisualTestContext, Receiver<Msg>) {
Expand Down Expand Up @@ -45,15 +70,7 @@ fn open_at(
cx,
)
});
let receiver = view.update(cx, |view, _| {
let (session, receiver) = session::test_session();
view.session = Some(session);
view.error = None;
view.exited = None;
view.exec_context = None;
view.suggest.suggest_env = crate::display::SuggestEnv::Wsl { distro: "Debian".into() };
receiver
});
let receiver = view.update(cx, |view, _| view.install_completion_test_session());
result = Some((view, receiver));
Root::new(cx.new(|_| Surface), window, cx)
});
Expand Down
150 changes: 150 additions & 0 deletions nebula_app/src/gpui_shell/workspace/keyboard_bindings/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,156 @@ mod dispatch {
}
}

#[gpui::test]
fn hybrid_completion_uses_window_tab_dispatch_in_both_workspace_layouts(
cx: &mut TestAppContext,
) {
use gpui::EntityInputHandler as _;
use nebula_terminal::event_loop::Msg;
use nebula_terminal::term::test::TermSize;
use nebula_terminal::vte::ansi::{Processor, StdSyncHandler};

let _settings_lock = crate::gpui_shell::settings_fixture::lock_theme_studio();
let _settings_bytes = crate::gpui_shell::settings_fixture::SettingsBytesGuard::capture();
nebula_settings::persist_keys(&[("ghost", "1".into()), ("theme", "Nord".into())]).unwrap();

fn draw(cx: &mut VisualTestContext) {
cx.run_until_parked();
cx.update(|window, cx| window.draw(cx).clear(cx));
cx.run_until_parked();
}

let (directory, workspace, mut cx) = open_workspace(0, cx);
cx.simulate_resize(size(px(1000.0), px(600.0)));
let (terminal, receiver) = cx.update(|window, cx| {
crate::gpui_shell::terminal::init(cx);
crate::gpui_shell::scientific_render::init(cx);
let mut settings =
crate::gpui_shell::config::Settings::load(nebula_settings::ThemeName::Nord);
settings.ghost = true;
settings.completion_style = crate::display::CompletionStyle::Inline;
cx.set_global(settings);
workspace.update(cx, |workspace, cx| {
workspace.add_terminal_with(
crate::session::LaunchSession::Shell {
name: "completion fixture".into(),
program: "pebrel-test-missing-shell-executable".into(),
args: Vec::new(),
},
Some(directory.path().to_owned()),
None,
window,
cx,
);
let terminal = workspace.tabs[workspace.active].focused_view().unwrap().clone();
let receiver =
terminal.update(cx, |view, _| view.install_completion_test_session());
(terminal, receiver)
})
});
let pane_id = terminal.read_with(&cx, |view, _| view.pane_id);
for position in [
nebula_settings::TabsPositionName::Sidebar,
nebula_settings::TabsPositionName::Top,
nebula_settings::TabsPositionName::Sidebar,
] {
workspace.update(&mut cx, |workspace, cx| {
workspace.tabs_position = position;
workspace.sync_settings_layout(true);
cx.notify();
});
draw(&mut cx);
cx.update(|window, cx| {
workspace.update(cx, |workspace, cx| workspace.open_settings(window, cx));
});
draw(&mut cx);
let settings = workspace.read_with(&cx, |workspace, _| {
workspace.settings_surface.as_ref().unwrap().0.clone()
});
for (style, value) in [
(crate::display::CompletionStyle::Inline, "inline"),
(crate::display::CompletionStyle::Hybrid, "hybrid"),
] {
nebula_settings::persist_keys(&[
("tabs_position", position.settings_value().into()),
("completion_style", value.into()),
])
.unwrap();
// 沿设置实体的真实订阅通知热应用,而不是直接给终端改模式。
settings.update(&mut cx, |_, cx| {
cx.emit(crate::gpui_shell::settings_pane::SettingsPaneEvent::Changed);
});
draw(&mut cx);
assert_eq!(
terminal.read_with(&cx, |view, _| view.completion_test_state().4),
style
);
assert_eq!(
workspace.read_with(&cx, |workspace, _| workspace.tabs_position),
position
);
}
let selector = if position == nebula_settings::TabsPositionName::Top {
"top-tab-0"
} else {
cx.update(|window, cx| {
workspace.update(cx, |workspace, cx| workspace.leave_settings(window, cx));
});
draw(&mut cx);
"sidebar-tab-0"
};
let tab = cx.debug_bounds(selector).expect("actual tab control is visible");
cx.simulate_click(tab.center(), gpui::Modifiers::default());
draw(&mut cx);
cx.update(|window, cx| {
assert!(terminal.read(cx).focus_handle.is_focused(window), "{position:?}");
assert!(!workspace.read(cx).settings_open);
});
press("ctrl-u", &mut cx);
receiver.try_iter().for_each(drop);
terminal.update(&mut cx, |view, cx| {
// 无 PTY 的夹具显式确认真实布局发来的网格尺寸,再模拟 shell 回显。
let size = TermSize::new(view.grid_cols(), view.grid_rows());
let mut term = view.session.as_ref().unwrap().term.lock();
term.resize(size);
Processor::<StdSyncHandler>::default()
.advance(&mut *term, "\x1b[2J\x1b[H❯ ".as_bytes());
cx.notify();
});
draw(&mut cx);
cx.update(|window, cx| {
terminal.update(cx, |view, cx| {
view.replace_text_in_range(None, "systemc", window, cx);
let mut term = view.session.as_ref().unwrap().term.lock();
Processor::<StdSyncHandler>::default().advance(&mut *term, b"systemc");
cx.notify();
});
});
receiver.try_iter().for_each(drop);
draw(&mut cx);
press("tab", &mut cx);
draw(&mut cx);
terminal.read_with(&cx, |view, _| {
let (line, requested, count, visible, _) = view.completion_test_state();
assert_eq!(view.pane_id, pane_id, "layout changes retain the same terminal");
assert_eq!(line, "systemc", "{position:?}");
assert!(
requested && count > 0 && visible,
"{position:?}: {requested}/{count}/{visible}"
);
});
assert!(receiver.try_iter().all(|message| !matches!(message, Msg::Input(_))));
cx.update(|window, cx| {
assert!(
terminal.read(cx).focus_handle.is_focused(window),
"Tab must retain terminal focus"
);
});
press("escape", &mut cx);
draw(&mut cx);
}
}

#[gpui::test]
fn digits_switch_real_tabs_and_focus_in_both_tab_layouts(cx: &mut TestAppContext) {
let (_directory, workspace, mut cx) = open_workspace(10, cx);
Expand Down
3 changes: 2 additions & 1 deletion scripts/tests/test_ci_native_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ def test_nextest_reserves_only_shared_settings_and_exact_bounded_git_fixtures(se
" or test(long_segments_use_a_real_dropdown_without_losing_preference_updates)"
" or test(font_size_click_input_commits_cancels_and_bounds_values)"
" or test(cjk_dropdown_selection_preserves_the_english_font_chain)"
" or test(review_regression_font_fields_align_and_dropdown_toggles_with_search)",
" or test(review_regression_font_fields_align_and_dropdown_toggles_with_search)"
" or test(=gpui_shell::workspace::keyboard_bindings::tests::dispatch::hybrid_completion_uses_window_tab_dispatch_in_both_workspace_layouts)",
"test-group": "theme-studio",
}, {
"filter": "test(=gpui_shell::terminal::view::startup_tests::"
Expand Down
Loading