fix(redis): display console command before response

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-08-15 21:55:21 +08:00
1 parent 6ec3ff0ccc
commit 60e685d6e6
2 files changed
+67 -14

No files matched your search

@@ -844,6 +844,58 @@ describe("RedisKeyBrowser command completion", () => {
});
});
describe("RedisKeyBrowser command console echo", () => {
it("echoes the submitted command to the terminal before the response arrives, then fills in the result", async () => {
const pending = deferred<{ value: unknown }>();
mocks.redisExecuteCommand.mockReturnValueOnce(pending.promise);
mountBrowser();
await settle();
await openCommandPanel();
await setCommandInput("PING");
const input = requiredElement<HTMLInputElement>("[data-redis-command-input]");
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
await settle();
// The command must show up in the terminal right away — while the
// request is still in flight — not only once the response resolves.
const terminal = requiredElement<HTMLElement>(".redis-command-terminal");
expect(terminal.textContent).toContain("PING");
expect(terminal.querySelectorAll(".mb-2")).toHaveLength(1);
expect(terminal.textContent).not.toContain("PONG");
pending.resolve({ value: "PONG" });
await settle();
expect(terminal.textContent).toContain("PONG");
// The result fills in the same echoed entry rather than adding a second one.
expect(terminal.querySelectorAll(".mb-2")).toHaveLength(1);
});
it("attaches a failed command's error to the same echoed entry", async () => {
const pending = deferred<{ value: unknown }>();
mocks.redisExecuteCommand.mockReturnValueOnce(pending.promise);
mountBrowser();
await settle();
await openCommandPanel();
await setCommandInput("PING");
const input = requiredElement<HTMLInputElement>("[data-redis-command-input]");
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
await settle();
const terminal = requiredElement<HTMLElement>(".redis-command-terminal");
expect(terminal.textContent).toContain("PING");
expect(terminal.querySelectorAll(".mb-2")).toHaveLength(1);
pending.reject(new Error("ERR unknown command"));
await settle();
expect(terminal.textContent).toContain("ERR unknown command");
expect(terminal.querySelectorAll(".mb-2")).toHaveLength(1);
});
});
describe("RedisKeyBrowser expiry creation", () => {
it.each(["string", "hash", "list", "set", "zset", "stream", "json"] as const)("writes %s before applying one relative TTL", async (type) => {
mountBrowser();
@@ -936,8 +936,15 @@ function scrollCommandTerminalToEnd() {
});
}
function appendCommandHistory(entry: Omit<RedisCommandHistoryEntry, "id">) {
commandHistory.value = [...commandHistory.value, { id: ++commandHistoryId, ...entry }];
function appendCommandHistory(entry: Omit<RedisCommandHistoryEntry, "id">): number {
const id = ++commandHistoryId;
commandHistory.value = [...commandHistory.value, { id, ...entry }];
scrollCommandTerminalToEnd();
return id;
}
function updateCommandHistory(id: number, patch: Partial<Omit<RedisCommandHistoryEntry, "id">>) {
commandHistory.value = commandHistory.value.map((entry) => (entry.id === id ? { ...entry, ...patch } : entry));
scrollCommandTerminalToEnd();
}
@@ -955,14 +962,13 @@ function appendCommandOutput(entry: Omit<RedisCommandHistoryEntry, "id">) {
async function runRedisCommand(command: string) {
const prompt = commandPrompt.value;
commandRunning.value = true;
// Echo the command to the terminal immediately so it doesn't look like the
// keystroke was lost while the request is in flight — the output is filled
// in on the same entry once the response (or error) arrives.
const entryId = appendCommandHistory({ prompt, command, output: "", error: false });
try {
const result = await api.redisExecuteCommand(props.connectionId, commandDb.value, command, !props.blockDangerousRedisCommands);
appendCommandHistory({
prompt,
command,
output: formatRedisConsoleValue(result.value),
error: false,
});
updateCommandHistory(entryId, { output: formatRedisConsoleValue(result.value), error: false });
// The db this command ran on — capture before nextRedisCommandDb() advances it.
const executedDb = commandDb.value;
commandDb.value = nextRedisCommandDb(commandDb.value, command, result.value);
@@ -980,12 +986,7 @@ async function runRedisCommand(command: string) {
persistRedisHistory(command, true, result.value);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
appendCommandHistory({
prompt,
command,
output: errorMessage,
error: true,
});
updateCommandHistory(entryId, { output: errorMessage, error: true });
// Persist failed command too
persistRedisHistory(command, false, null, errorMessage);
} finally {