DB & Dev Tools · APIs: requests, answers, environments, sockets and GraphQL

ApiRequestBuilder builds a request: its method and address, then tabs
for the query's pairs, headers, body and sign-in, with the address that
goes out under the field, the environment's {{variables}} filled in or
the missing ones named. Send hands the owner the request. The query's
pairs go through forms::KeyValueInput. ResponseViewer shows an answer's
status in its class's tone with its words, its time and size, then its
body, JSON indented, or its headers in a HeadersTable.
EnvironmentSelector picks a set of variables and masks secrets until
their eye opens them. CollectionTree holds saved requests in folders;
Enter opens one. WebSocketConsole shows a socket's messages, each
marked sent or received with its time, and sends on Enter while the
socket is open and the field holds words. GraphQLExplorer browses a
schema beside a query and its variables; Run waits for variables that
read as JSON and the answer shows as a tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-27 10:24:44 -04:00
1 parent 54eb947b34
commit 4550c87bf4
12 files changed
+1963 -8

No files matched your search

+167
View File
@@ -0,0 +1,167 @@
use gpui::{
AnyElement, App, AppContext as _, Entity, IntoElement, ParentElement, Styled, TestAppContext,
Window, div, px,
};
use super::{Bench, bench, said, say, settle, tab, tap};
use crate::{
devtools::{
ApiRequest, ApiRequestBuilder, Auth, CollectionTree, GraphQLExplorer, Method, Saved,
SocketState, WebSocketConsole,
},
editor::CodeEditor,
forms::TextInput,
};
fn builder(_: &Bench, _: &mut Window, _: &mut App, owner: Entity<Bench>) -> AnyElement {
let mut request = ApiRequest::new("r", Method::Get, "https://{{host}}/items");
request.params = vec![("q".into(), "red shoes".into())];
request.auth = Auth::Basic {
user: "ada".into(),
password: "pw".into(),
};
ApiRequestBuilder::new("builder", request)
.variables([("host", "api.example.com")])
.on_send(move |request, _, cx| {
say(
&owner,
format!(
"{} {} {:?}",
request.method.name(),
request.address(),
request.auth
),
cx,
)
})
.into_any_element()
}
/// Stops: the method, the address, then Send.
#[gpui::test]
fn send_hands_on_the_request_with_its_pairs(cx: &mut TestAppContext) {
let (host, cx) = bench(builder, cx);
tab(3, cx);
tap("space", cx);
assert_eq!(
said(&host, cx),
[r#"GET https://{{host}}/items?q=red%20shoes Basic { user: "ada", password: "pw" }"#]
);
}
fn console(bench: &Bench, window: &mut Window, cx: &mut App, owner: Entity<Bench>) -> AnyElement {
let draft = window.use_keyed_state("draft", cx, TextInput::new);
let now: jiff::Timestamp = "2026-09-27T12:00:00Z".parse().expect("a time");
let state = match bench.choice {
0 => SocketState::Open,
_ => SocketState::Closed,
};
let toggled = owner.clone();
WebSocketConsole::new("socket", "wss://example.com", state, [], &draft, now)
.on_send(move |text, _, cx| say(&owner, format!("send {text}"), cx))
.on_toggle(move |_, cx| say(&toggled, "toggle".into(), cx))
.into_any_element()
}
/// Stops: Disconnect, the message field, then Send once it holds words.
#[gpui::test]
fn enter_in_the_field_sends_and_empties_it(cx: &mut TestAppContext) {
let (host, cx) = bench(console, cx);
tab(2, cx);
cx.simulate_input("ping");
tap("enter", cx);
tap("enter", cx);
assert_eq!(
said(&host, cx),
["send ping"],
"an empty field sends nothing"
);
}
/// Stops while closed: Connect, then the field; Send rests, so a third Tab wraps to Connect.
#[gpui::test]
fn a_closed_socket_sends_nothing_and_connect_asks_the_owner(cx: &mut TestAppContext) {
let (host, cx) = bench(console, cx);
host.update(cx, |bench, cx| {
bench.choice = 1;
cx.notify();
});
settle(cx);
tab(2, cx);
cx.simulate_input("ping");
tap("enter", cx);
tab(3, cx);
tap("space", cx);
assert_eq!(said(&host, cx), ["toggle"]);
}
fn saved(_: &Bench, _: &mut Window, _: &mut App, owner: Entity<Bench>) -> AnyElement {
let list = Saved::Request {
key: "list".into(),
name: "List orders".into(),
method: Method::Get,
};
let orders = Saved::Folder {
key: "orders".into(),
name: "Orders".into(),
items: vec![list],
};
div()
.h(px(200.0))
.child(
CollectionTree::new("saved", [orders])
.open(["orders"])
.on_open(move |key, _, cx| say(&owner, format!("open {key}"), cx)),
)
.into_any_element()
}
/// Stops: the tree, its cursor on the folder.
#[gpui::test]
fn enter_on_a_saved_request_opens_it(cx: &mut TestAppContext) {
let (host, cx) = bench(saved, cx);
tab(1, cx);
tap("down", cx);
tap("enter", cx);
assert_eq!(said(&host, cx), ["open list"]);
}
fn graph(bench: &Bench, window: &mut Window, cx: &mut App, owner: Entity<Bench>) -> AnyElement {
let (Some(variables), query) = (
bench.editor.clone(),
window.use_keyed_state("query", cx, |window, cx| {
CodeEditor::new("{ a }", window, cx)
}),
) else {
return div().into_any_element();
};
let search = window.use_keyed_state("search", cx, TextInput::new);
GraphQLExplorer::new("graph", [], &query, &variables, &search)
.on_run(move |query, variables, _, cx| say(&owner, format!("{query} {variables}"), cx))
.into_any_element()
}
/// Stops: the schema, the query, the variables, then Run while they read.
#[gpui::test]
fn run_waits_for_variables_that_read(cx: &mut TestAppContext) {
let (host, cx) = bench(graph, cx);
let variables = cx.update(|window, cx| cx.new(|cx| CodeEditor::new("{", window, cx)));
host.update(cx, |bench, cx| {
bench.editor = Some(variables.clone());
cx.notify();
});
settle(cx);
tab(4, cx);
tap("space", cx);
assert!(
said(&host, cx).is_empty(),
"variables that do not read keep Run at rest"
);
variables.update(cx, |editor, cx| {
editor.set_text(r#"{"id": 1}"#.to_string(), cx)
});
settle(cx);
tab(4, cx);
tap("space", cx);
assert_eq!(said(&host, cx), [r#"{ a } {"id": 1}"#]);
}