import { assertEquals } from "@std/assert"; import { press, withDom } from "@berry/util/dom.ts"; import { conversationName } from "./conversation-name.ts"; Deno.test("sidebar names edit inline, save deliberately, and retain failed edits", async (t) => { await withDom(async (window) => { const row = document.createElement("div"); const link = document.createElement("a"); link.href = "#/conversation"; const label = document.createElement("span"); link.append(label); const saved: (string | null)[] = []; let save = (_title: string | null) => Promise.resolve(); const name = conversationName(link, label, (title) => { saved.push(title); return save(title); }); row.append(name.editor, link); document.body.append(row); const start = () => link.dispatchEvent( new window.MouseEvent("dblclick", { cancelable: true }) as unknown as MouseEvent, ); const key = (value: string) => !press(name.editor, value, { bubbles: false }); const settle = () => window.happyDOM.waitUntilComplete(); try { await t.step("double-click opens an input holding the shown name", () => { name.update(undefined, "first message preview"); assertEquals(label.textContent, "first message preview"); assertEquals(name.editor.hidden, true); assertEquals(start(), false); assertEquals(link.hidden, true); assertEquals(name.editor.hidden, false); assertEquals(name.editor.value, "first message preview"); assertEquals(document.activeElement, name.editor); }); await t.step("Escape cancels without saving and restores keyboard focus", async () => { name.editor.value = "cancel this"; key("Escape"); await settle(); assertEquals(saved, []); assertEquals(link.hidden, false); assertEquals(name.editor.hidden, true); assertEquals(label.textContent, "first message preview"); assertEquals(document.activeElement, link); }); await t.step( "Enter trims and saves once even though restoring focus blurs the input", async () => { start(); name.editor.value = " renamed conversation "; key("Enter"); await settle(); assertEquals(saved, ["renamed conversation"]); assertEquals(label.textContent, "renamed conversation"); assertEquals(name.editor.hidden, true); assertEquals(link.hidden, false); }, ); await t.step("blur saves changes without stealing focus from another control", async () => { start(); name.editor.value = "saved on blur"; const elsewhere = document.createElement("input"); document.body.append(elsewhere); elsewhere.focus(); await settle(); assertEquals(saved, ["renamed conversation", "saved on blur"]); assertEquals(document.activeElement, elsewhere); assertEquals(label.textContent, "saved on blur"); }); await t.step( "leaving the generated preview unchanged does not create a custom name", async () => { name.update(undefined, "automatic preview"); start(); key("Enter"); await settle(); assertEquals(saved.length, 2); name.update(undefined, "updated automatic preview"); assertEquals(label.textContent, "updated automatic preview"); }, ); await t.step("clearing a custom name returns to the message preview", async () => { name.update("custom", "automatic preview"); start(); name.editor.value = " "; key("Enter"); await settle(); assertEquals(saved.at(-1), null); assertEquals(label.textContent, "automatic preview"); }); await t.step("live sidebar updates do not overwrite an unfinished edit", async () => { name.update("before", "preview"); start(); name.editor.value = "my unfinished name"; name.update("renamed on another device", "changed preview"); assertEquals(name.editor.value, "my unfinished name"); key("Escape"); await settle(); assertEquals(label.textContent, "renamed on another device"); assertEquals(saved.length, 3); }); await t.step("failed saves keep the input editable and can be retried", async () => { save = () => Promise.reject(new Error("offline")); start(); name.editor.value = "retry this"; key("Enter"); await settle(); assertEquals(name.editor.hidden, false); assertEquals(name.editor.value, "retry this"); assertEquals(name.editor.readOnly, false); assertEquals(name.editor.getAttribute("aria-invalid"), "true"); assertEquals(name.editor.title, "offline"); save = () => Promise.resolve(); key("Enter"); await settle(); assertEquals(name.editor.hidden, true); assertEquals(label.textContent, "retry this"); }); await t.step("F2 provides a keyboard equivalent for double-click", async () => { link.dispatchEvent( new window.KeyboardEvent("keydown", { key: "F2" }) as unknown as KeyboardEvent, ); assertEquals(name.editor.hidden, false); assertEquals(document.activeElement, name.editor); key("Escape"); await settle(); }); await t.step( "a pending save cannot overwrite a newer name received from another device", async () => { const pending = Promise.withResolvers(); save = () => pending.promise; start(); name.editor.value = "earlier request"; key("Enter"); const count = saved.length; key("Enter"); assertEquals(saved.length, count); name.update("newer remote name", "preview"); pending.resolve(); await settle(); assertEquals(label.textContent, "newer remote name"); }, ); } finally { name.dispose(); } }); });