feat: chat — saisie auto-agrandissante, accusé de réception ✓✓, vignette de tuile #192 (BUG-109)
This commit is contained in:
@@ -25,7 +25,7 @@ const dom = new JSDOM(
|
||||
<div class="sidebar-tab-panel active" id="sidebar-panel-chat">
|
||||
<div id="sidebar-panel-chat-list" class="file-chat-list"></div>
|
||||
<div id="sidebar-panel-chat-pending"></div>
|
||||
<form id="sidebar-panel-chat-form"><input id="sidebar-panel-chat-input" /></form>
|
||||
<form id="sidebar-panel-chat-form"><textarea id="sidebar-panel-chat-input"></textarea></form>
|
||||
</div>
|
||||
<button id="sidebar-tab-chat"></button>
|
||||
</body></html>`,
|
||||
@@ -77,7 +77,7 @@ const mod = await import("../../frontend/js/filechat.js");
|
||||
const {
|
||||
renderChatMessages, onChatMessage, openFileChat, closeFileChat, toggleFileChat,
|
||||
followFileChat, openSidebarChat, filterChatMessages, refreshUnreadBadge,
|
||||
onChatDeleted,
|
||||
onChatDeleted, onChatRead,
|
||||
} = mod;
|
||||
|
||||
let pass = 0;
|
||||
@@ -278,6 +278,66 @@ test("#191 — DM routed by pair path, isolated from general", async () => {
|
||||
assert.strictEqual(list.querySelectorAll(".file-chat-msg").length, before);
|
||||
});
|
||||
|
||||
// --- #192 : accusé de réception + boîte de saisie auto-agrandie --------------
|
||||
|
||||
test("#192 — receipt shows ✓ then ✓✓ once another participant read it", () => {
|
||||
const list = document.createElement("div");
|
||||
renderChatMessages(list, [{ id: "r1", user: "bruno", text: "coucou", ts: 100 }], "bruno", null, {});
|
||||
let st = list.querySelector(".file-chat-status");
|
||||
assert.strictEqual(st.textContent, "✓");
|
||||
assert.strictEqual(st.classList.contains("read"), false);
|
||||
// MY OWN read timestamp must never count as "the peer read it"
|
||||
renderChatMessages(list, [{ id: "r2", user: "bruno", text: "coucou", ts: 100 }], "bruno", null, { bruno: 500 });
|
||||
assert.strictEqual(list.querySelector(".file-chat-status").textContent, "✓");
|
||||
renderChatMessages(list, [{ id: "r3", user: "bruno", text: "coucou", ts: 100 }], "bruno", null, { alice: 150 });
|
||||
st = list.querySelector(".file-chat-status");
|
||||
assert.strictEqual(st.textContent, "✓✓");
|
||||
assert.strictEqual(st.classList.contains("read"), true);
|
||||
// messages of others carry no receipt
|
||||
renderChatMessages(list, [{ id: "r4", user: "alice", text: "ok", ts: 100 }], "bruno", null, {});
|
||||
assert.strictEqual(list.querySelector(".file-chat-status"), null);
|
||||
});
|
||||
|
||||
test("#192 — onChatRead flips ✓ → ✓✓ live and notifies the sender", async () => {
|
||||
await openSidebarChat();
|
||||
const list = document.getElementById("sidebar-panel-chat-list");
|
||||
renderChatMessages(list, [{ id: "s1", user: "bruno", text: "msg", ts: 200 }], "bruno", "/api/chat", {});
|
||||
assert.strictEqual(list.querySelector(".file-chat-status").textContent, "✓");
|
||||
const container = document.getElementById("toast-container");
|
||||
const before = container.children.length;
|
||||
onChatRead({ vault: "__global__", path: "general", user: "alice", ts: 300, read: { bruno: 250, alice: 300 } });
|
||||
assert.strictEqual(list.querySelector(".file-chat-status").textContent, "✓✓");
|
||||
assert.ok(container.children.length > before, "notification de lecture affichée");
|
||||
// notre propre écho ne notifie pas
|
||||
const same = container.children.length;
|
||||
onChatRead({ vault: "__global__", path: "general", user: "bruno", ts: 310, read: { bruno: 310 } });
|
||||
assert.strictEqual(container.children.length, same);
|
||||
});
|
||||
|
||||
test("#192 — saisie en textarea auto-agrandie, Enter envoie", async () => {
|
||||
assert.strictEqual(document.getElementById("sidebar-panel-chat-input").tagName, "TEXTAREA");
|
||||
await openFileChat("V", "grow.md");
|
||||
const panel = document.getElementById("file-chat-panel");
|
||||
const input = panel.querySelector(".file-chat-input");
|
||||
assert.strictEqual(input.tagName, "TEXTAREA");
|
||||
assert.strictEqual(input.rows, 1);
|
||||
input.value = "ligne1\nligne2";
|
||||
input.dispatchEvent(new w.Event("input", { bubbles: true }));
|
||||
assert.ok(input.style.height, "hauteur pilotée en JS");
|
||||
let prevented = false;
|
||||
const ev = new w.KeyboardEvent("keydown", { key: "Enter", bubbles: true, cancelable: true });
|
||||
Object.defineProperty(ev, "preventDefault", { value: () => { prevented = true; } });
|
||||
input.value = ""; // rien à envoyer : on ne teste que le geste
|
||||
input.dispatchEvent(ev);
|
||||
assert.strictEqual(prevented, true, "Enter (sans Shift) est intercepté");
|
||||
const nl = new w.KeyboardEvent("keydown", { key: "Enter", shiftKey: true, bubbles: true, cancelable: true });
|
||||
let nlPrevented = false;
|
||||
Object.defineProperty(nl, "preventDefault", { value: () => { nlPrevented = true; } });
|
||||
input.dispatchEvent(nl);
|
||||
assert.strictEqual(nlPrevented, false, "Shift+Enter garde le retour à la ligne");
|
||||
closeFileChat();
|
||||
});
|
||||
|
||||
// Sequential execution: each case depends on panel state left by the previous.
|
||||
for (const [name, fn] of cases) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user