Files
Ely-GPUI-Components/src/messaging/tests/mod.rs
T
ZacharyZhang-NYandClaude Opus 5.5 94deeaed52 Port · Build: Zed's gpui at 1a28cff
gpui and gpui_platform now come from Zed's repo by git at 1a28cff,
the code gpui-pre 0.3.7 republishes, so the gallery can later build
for the browser. gpui takes stacker, as 0.2.2 always did, and
gpui_platform takes font-kit, without which macOS draws no text;
runtime_shaders moves to gpui_platform.

The API moved: focus, blur and Tab steps take cx, Corner is Anchor,
shaped lines paint with an alignment, defer_draw takes a content
mask, the inspector registers a factory, async updates return their
value, and wrapped lines are shared, not cloned. Updates through a
window context go through the window, so a closed window still ends
them as it did.

Where the new gpui behaves differently, Ely keeps its old intent.
Holds and reveals under a resting pointer survive typing through
gpui's input-modality-independent hover; previews the keyboard also
moves keep the default, and the calendar's leave ignores a key press.
Enter reaches a button inside the code editor, since gpui now arms a
key click on the press. The event logger skips a lone modifier. A
video and an image grid keep their shape in a flex column under
taffy 0.13, as a picture does.

AGENTS.md records the new stack and the rules this port changes; the
rest of the old gpui and taffy rules are re-tested in T44c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:30:17 -04:00

281 lines
8.3 KiB
Rust

use gpui::{
Context, Entity, IntoElement, KeyBinding, KeyUpEvent, Keystroke, Modifiers, ParentElement,
Render, SharedString, Styled, TestAppContext, VisualTestContext, Window, div, px,
};
use jiff::{SignedDuration, Timestamp};
use super::{
ChannelHeader, ChannelItem, ChannelList, DirectMessageItem, PinnedMessage, PinnedMessages,
};
use crate::{data_display::Presence, primitives::FocusNext, theme::Theme};
mod calls;
mod messages;
mod people;
mod pickers;
fn setup(cx: &mut TestAppContext) {
cx.update(|cx| {
Theme::init(cx);
Theme::update(cx, |theme| theme.reduced_motion = true);
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
});
}
fn settle(cx: &mut VisualTestContext) {
for _ in 0..3 {
cx.run_until_parked();
cx.update(|window, _| window.refresh());
}
cx.run_until_parked();
}
fn press(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
settle(cx);
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse(key).expect("a key"),
});
settle(cx);
}
/// Moves focus to the `nth` Tab stop from none.
fn tab_to(nth: usize, cx: &mut VisualTestContext) {
cx.update(|window, cx| {
window.blur(cx);
for _ in 0..nth {
window.focus_next(cx);
}
});
settle(cx);
}
/// Channels and direct messages with design open, and the chats it opened.
struct Chatting {
opened: Vec<SharedString>,
}
impl Render for Chatting {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let owner = cx.entity();
let channels = [
("general", ChannelItem::new("general", "general")),
("design", ChannelItem::new("design", "design").unread(3)),
(
"random",
ChannelItem::new("random", "random").unread(9).muted(true),
),
];
let people = [
(
"ana",
DirectMessageItem::new("ana", "Ana Lima", Presence::Online),
),
(
"ben",
DirectMessageItem::new("ben", "Ben Ito", Presence::Offline).unread(2),
),
];
div().w(px(260.0)).child(
ChannelList::new("chats")
.section("Channels", channels)
.section("Direct messages", people)
.open("design")
.on_open(move |key, _, cx| {
owner.update(cx, |host, _| host.opened.push(key.clone()))
}),
)
}
}
#[gpui::test]
fn each_section_opens_by_key_and_starts_on_the_chat_open(cx: &mut TestAppContext) {
setup(cx);
let (host, cx) = cx.add_window_view(|_, _| Chatting { opened: Vec::new() });
cx.update(|window, _| window.activate_window());
settle(cx);
tab_to(1, cx);
press("down", cx);
tab_to(2, cx);
press("down", cx);
assert_eq!(
host.read_with(cx, |host, _| host.opened.clone()),
["random", "ben"]
);
}
#[gpui::test]
fn enter_opens_the_chat_under_the_cursor(cx: &mut TestAppContext) {
setup(cx);
let (host, cx) = cx.add_window_view(|_, _| Chatting { opened: Vec::new() });
cx.update(|window, _| window.activate_window());
settle(cx);
tab_to(2, cx);
press("enter", cx);
assert_eq!(host.read_with(cx, |host, _| host.opened.clone()), ["ana"]);
}
#[test]
#[should_panic(expected = "chat general twice")]
fn a_chat_is_listed_once() {
let _ = ChannelList::new("chats")
.section("Channels", [("general", ChannelItem::new("a", "general"))])
.section("Starred", [("general", ChannelItem::new("b", "general"))]);
}
/// A header with members, pins and a call, and what it asked.
struct Heading {
asked: Vec<&'static str>,
}
impl Render for Heading {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (members, pins, call) = (cx.entity(), cx.entity(), cx.entity());
div().w(px(480.0)).child(
ChannelHeader::new("header", "design")
.topic("Where the atrium takes its shape")
.members(12, move |_, cx| {
members.update(cx, |host, _| host.asked.push("members"))
})
.pins(3, move |_, cx| {
pins.update(cx, |host, _| host.asked.push("pins"))
})
.on_call(move |_, cx| call.update(cx, |host, _| host.asked.push("call"))),
)
}
}
#[gpui::test]
fn the_headers_actions_each_ask_their_own(cx: &mut TestAppContext) {
setup(cx);
let (host, cx) = cx.add_window_view(|_, _| Heading { asked: Vec::new() });
cx.update(|window, _| window.activate_window());
settle(cx);
for nth in 1..=3 {
tab_to(nth, cx);
press("space", cx);
}
assert_eq!(
host.read_with(cx, |host, _| host.asked.clone()),
["members", "pins", "call"]
);
}
/// Two pins, the later first, and what was asked.
struct Pinning {
asked: Vec<String>,
}
impl Render for Pinning {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (open, unpin) = (cx.entity(), cx.entity());
let now = Timestamp::now();
let pin = |key: &str, hours: i64| PinnedMessage {
key: SharedString::from(key.to_string()),
author: "Ana Lima".into(),
at: now - SignedDuration::from_hours(hours),
text: SharedString::from(format!("The {key} note")),
};
div().w(px(360.0)).child(
PinnedMessages::new("pins", [pin("older", 30), pin("newer", 2)])
.on_open(move |key, _, cx| {
open.update(cx, |host, _| host.asked.push(format!("open {key}")))
})
.on_unpin(move |key, _, cx| {
unpin.update(cx, |host, _| host.asked.push(format!("unpin {key}")))
}),
)
}
}
fn pinning(cx: &mut TestAppContext) -> (Entity<Pinning>, &mut VisualTestContext) {
setup(cx);
let (host, cx) = cx.add_window_view(|_, _| Pinning { asked: Vec::new() });
cx.update(|window, _| window.activate_window());
settle(cx);
(host, cx)
}
#[gpui::test]
fn the_newest_pin_comes_first_and_enter_opens_it(cx: &mut TestAppContext) {
let (host, cx) = pinning(cx);
tab_to(1, cx);
press("enter", cx);
assert_eq!(
host.read_with(cx, |host, _| host.asked.clone()),
["open newer"]
);
}
#[gpui::test]
fn a_press_on_unpin_unpins_and_opens_nothing(cx: &mut TestAppContext) {
let (host, cx) = pinning(cx);
let unpin = cx
.debug_bounds("unpin older")
.expect("the older pin's unpin draws");
cx.simulate_click(unpin.center(), Modifiers::none());
settle(cx);
assert_eq!(
host.read_with(cx, |host, _| host.asked.clone()),
["unpin older"]
);
}
#[gpui::test]
fn space_on_unpin_unpins_and_opens_nothing(cx: &mut TestAppContext) {
let (host, cx) = pinning(cx);
tab_to(2, cx);
press("space", cx);
assert_eq!(
host.read_with(cx, |host, _| host.asked.clone()),
["unpin newer"]
);
}
#[test]
#[should_panic(expected = "pin a twice")]
fn a_message_is_pinned_once() {
let pin = PinnedMessage {
key: "a".into(),
author: "Ana".into(),
at: Timestamp::UNIX_EPOCH,
text: "hi".into(),
};
let _ = PinnedMessages::new("pins", [pin.clone(), pin]);
}
#[test]
#[should_panic(expected = "section Channels twice")]
fn a_section_is_named_once() {
let _ = ChannelList::new("chats")
.section("Channels", [("a", ChannelItem::new("a", "a"))])
.section("Channels", [("b", ChannelItem::new("b", "b"))]);
}
#[test]
#[should_panic(expected = "section Starred holds no chats")]
fn a_section_holds_a_chat() {
let _ = ChannelList::new("chats").section("Starred", Vec::<(&str, ChannelItem)>::new());
}
/// No pins, and nothing else to focus.
struct Unpinned;
impl Render for Unpinned {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.w(px(360.0))
.child(PinnedMessages::new("pins", Vec::new()))
}
}
#[gpui::test]
fn nothing_pinned_takes_no_tab_stop(cx: &mut TestAppContext) {
setup(cx);
let (_, cx) = cx.add_window_view(|_, _| Unpinned);
cx.update(|window, _| window.activate_window());
settle(cx);
tab_to(1, cx);
assert!(cx.update(|window, cx| window.focused(cx).is_none()));
}