Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/improsocial/impro
An extensible Bluesky client for web impro.social
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484import { describe, it, mock } from "node:test";import assert from "node:assert/strict";import { showPluginModal as _showPluginModal, updatePluginModal as _updatePluginModal, hidePluginModal, showPluginInstallPermissionsModal, showPluginFetchPermissionModal,} from "/js/plugins/pluginModal.js";import { PluginRenderer } from "/js/plugins/pluginRendering.js";import { respondToConfirm, waitFor } from "../../testHelpers.js";
function showPluginModal(opts) { const pluginRenderer = new PluginRenderer(null, opts.pluginId); return _showPluginModal({ pluginRenderer, ...opts });}
function updatePluginModal(opts) { const pluginRenderer = new PluginRenderer(null, opts.pluginId); return _updatePluginModal({ pluginRenderer, ...opts });}
function clearDOM() { document.body.innerHTML = "";}
let pluginModalCounter = 0;function uniqueModalId(prefix) { pluginModalCounter += 1; return `${prefix}-${pluginModalCounter}`;}
describe("showPluginModal", () => { it("should create a dialog with plugin-modal class and pluginId dataset", () => { clearDOM(); showPluginModal({ pluginId: "test.plugin", modalId: uniqueModalId("create"), title: { tag: "span", text: "Hello" }, content: { tag: "div", text: "Body" }, }); const dialog = document.querySelector("dialog.plugin-modal"); assert(dialog !== null); assert.deepEqual(dialog.dataset.pluginId, "test.plugin"); assert(dialog.classList.contains("bottom-sheet")); assert(dialog.hasAttribute("open")); });
it("should take the initial focus itself so content is not scrolled into view", () => { clearDOM(); showPluginModal({ pluginId: "p", modalId: uniqueModalId("autofocus"), title: { tag: "span", text: "T" }, content: { tag: "div", children: [{ tag: "button", text: "Deep button" }], }, }); const dialog = document.querySelector("dialog.plugin-modal"); assert(dialog.hasAttribute("autofocus")); });
it("should render the body as a scroll region with the title outside it", () => { clearDOM(); showPluginModal({ pluginId: "p", modalId: uniqueModalId("scroll"), title: { tag: "span", text: "T" }, content: { tag: "div", text: "Body" }, }); const body = document.querySelector(".plugin-modal-body"); assert(body !== null); assert(body.classList.contains("sheet-scroll-region")); assert(body.classList.contains("plugin-content")); const title = document.querySelector(".modal-dialog-title"); assert(title.parentElement === body.parentElement); assert(body.querySelector(".modal-dialog-title") === null); });
it("should render the title with the modal-dialog-title class", () => { clearDOM(); showPluginModal({ pluginId: "p", modalId: uniqueModalId("title"), title: { tag: "span", text: "My Title" }, content: { tag: "div", text: "Body" }, }); const title = document.querySelector(".modal-dialog-title"); assert(title !== null); assert.deepEqual(title.textContent, "My Title"); });
it("should skip the title when it is empty", () => { clearDOM(); showPluginModal({ pluginId: "p", modalId: uniqueModalId("no-title"), title: { tag: "span", text: "" }, content: { tag: "div", text: "Body only" }, }); const title = document.querySelector(".modal-dialog-title"); assert(title === null); });
it("should render content children when content has children", () => { clearDOM(); showPluginModal({ pluginId: "p", modalId: uniqueModalId("children"), title: { tag: "span", text: "T" }, content: { tag: "div", children: [ { tag: "p", text: "First" }, { tag: "p", text: "Second" }, ], }, }); const paragraphs = document.querySelectorAll(".plugin-modal-body > p"); assert.deepEqual(paragraphs.length, 2); assert.deepEqual(paragraphs[0].textContent, "First"); assert.deepEqual(paragraphs[1].textContent, "Second"); });
it("should render the content node directly when it has no children", () => { clearDOM(); showPluginModal({ pluginId: "p", modalId: uniqueModalId("single"), title: { tag: "span", text: "T" }, content: { tag: "p", text: "Single body" }, }); const body = document.querySelector(".plugin-modal-body > p"); assert(body !== null); assert.deepEqual(body.textContent, "Single body"); });
it("should show a modal with new content when reopened after hiding", async () => { clearDOM(); const pluginId = "reuse.plugin"; const modalId = uniqueModalId("reuse"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "First Title" }, content: { tag: "p", text: "First body" }, }); hidePluginModal({ pluginId, modalId }); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "Second Title" }, content: { tag: "p", text: "Second body" }, }); await new Promise((resolve) => setTimeout(resolve, 0)); const dialogs = document.querySelectorAll("dialog.plugin-modal"); assert.deepEqual(dialogs.length, 1); const title = document.querySelector(".modal-dialog-title"); assert.deepEqual(title.textContent, "Second Title"); const body = document.querySelector(".plugin-modal-body > p"); assert.deepEqual(body.textContent, "Second body"); assert(dialogs[0].hasAttribute("open")); });
it("should not reopen a queued modal that was hidden while closing", async () => { clearDOM(); const pluginId = "reuse.plugin"; const modalId = uniqueModalId("queued-hide"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "First Title" }, content: { tag: "p", text: "First body" }, }); hidePluginModal({ pluginId, modalId }); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "Second Title" }, content: { tag: "p", text: "Second body" }, }); hidePluginModal({ pluginId, modalId }); await new Promise((resolve) => setTimeout(resolve, 0)); assert(document.querySelector("dialog.plugin-modal[open]") === null); });
it("should be a no-op when called with the same key while already open", () => { clearDOM(); const pluginId = "noop.plugin"; const modalId = uniqueModalId("noop"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "Original" }, content: { tag: "p", text: "Original body" }, }); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "Replaced" }, content: { tag: "p", text: "Replaced body" }, }); const title = document.querySelector(".modal-dialog-title"); assert.deepEqual(title.textContent, "Original"); hidePluginModal({ pluginId, modalId }); });
it("should invoke onDismiss when dismissed via backdrop click", async () => { clearDOM(); const onDismiss = mock.fn(); showPluginModal({ pluginId: "backdrop.plugin", modalId: uniqueModalId("backdrop"), title: { tag: "span", text: "T" }, content: { tag: "p", text: "B" }, onDismiss, }); const dialog = document.querySelector("dialog.plugin-modal"); dialog.dispatchEvent(new Event("click", { bubbles: true })); await Promise.resolve(); assert(!dialog.hasAttribute("open")); assert.deepEqual(onDismiss.mock.callCount(), 1); });
it("should invoke onDismiss when dismissed via cancel event", async () => { clearDOM(); const onDismiss = mock.fn(); showPluginModal({ pluginId: "cancel.plugin", modalId: uniqueModalId("cancel"), title: { tag: "span", text: "T" }, content: { tag: "p", text: "B" }, onDismiss, }); const dialog = document.querySelector("dialog.plugin-modal"); const cancelEvent = new Event("cancel"); cancelEvent.preventDefault = () => {}; dialog.dispatchEvent(cancelEvent); await Promise.resolve(); assert(!dialog.hasAttribute("open")); assert.deepEqual(onDismiss.mock.callCount(), 1); });
it("should not require an onDismiss callback", () => { clearDOM(); showPluginModal({ pluginId: "no-cb.plugin", modalId: uniqueModalId("no-cb"), title: { tag: "span", text: "T" }, content: { tag: "p", text: "B" }, }); const dialog = document.querySelector("dialog.plugin-modal"); dialog.dispatchEvent(new Event("click", { bubbles: true })); assert(!dialog.hasAttribute("open")); });
it("should isolate modals by pluginId/modalId key", () => { clearDOM(); const modalIdA = uniqueModalId("isoA"); const modalIdB = uniqueModalId("isoB"); showPluginModal({ pluginId: "iso.plugin", modalId: modalIdA, title: { tag: "span", text: "A" }, content: { tag: "p", text: "A body" }, }); showPluginModal({ pluginId: "iso.plugin", modalId: modalIdB, title: { tag: "span", text: "B" }, content: { tag: "p", text: "B body" }, }); const dialogs = document.querySelectorAll("dialog.plugin-modal"); assert.deepEqual(dialogs.length, 2); hidePluginModal({ pluginId: "iso.plugin", modalId: modalIdA }); hidePluginModal({ pluginId: "iso.plugin", modalId: modalIdB }); });});
describe("updatePluginModal", () => { it("should patch existing content in place, preserving element identity", () => { clearDOM(); const pluginId = "update.plugin"; const modalId = uniqueModalId("patch"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "p", text: "One" }, }); const body = document.querySelector(".plugin-modal-body > p"); updatePluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "p", text: "Two" }, }); const updated = document.querySelector(".plugin-modal-body > p"); assert(updated === body); assert.deepEqual(updated.textContent, "Two"); hidePluginModal({ pluginId, modalId }); });
it("should add and remove content children across updates", () => { clearDOM(); const pluginId = "update.plugin"; const modalId = uniqueModalId("children"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "div", children: [{ tag: "p", text: "First" }] }, }); updatePluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "div", children: [ { tag: "p", text: "First" }, { tag: "p", text: "Second" }, ], }, }); let paragraphs = document.querySelectorAll(".plugin-modal-body > p"); assert.deepEqual(paragraphs.length, 2); assert.deepEqual(paragraphs[1].textContent, "Second"); updatePluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "div", children: [{ tag: "p", text: "Only" }] }, }); paragraphs = document.querySelectorAll(".plugin-modal-body > p"); assert.deepEqual(paragraphs.length, 1); assert.deepEqual(paragraphs[0].textContent, "Only"); hidePluginModal({ pluginId, modalId }); });
it("should toggle the title on and off across updates", () => { clearDOM(); const pluginId = "update.plugin"; const modalId = uniqueModalId("title-toggle"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "" }, content: { tag: "p", text: "Body" }, }); assert(document.querySelector(".modal-dialog-title") === null); updatePluginModal({ pluginId, modalId, title: { tag: "span", text: "Now titled" }, content: { tag: "p", text: "Body" }, }); const title = document.querySelector(".modal-dialog-title"); assert(title !== null); assert.deepEqual(title.textContent, "Now titled"); updatePluginModal({ pluginId, modalId, title: { tag: "span", text: "" }, content: { tag: "p", text: "Body" }, }); assert(document.querySelector(".modal-dialog-title") === null); hidePluginModal({ pluginId, modalId }); });
it("should be a no-op when no modal exists for the key", () => { clearDOM(); updatePluginModal({ pluginId: "missing.plugin", modalId: "missing-modal", title: { tag: "span", text: "T" }, content: { tag: "p", text: "B" }, }); assert(document.querySelector("dialog") === null); });
it("should be a no-op after the modal is hidden", () => { clearDOM(); const pluginId = "update.plugin"; const modalId = uniqueModalId("after-hide"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "p", text: "Original" }, }); hidePluginModal({ pluginId, modalId }); updatePluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "p", text: "Changed" }, }); const dialog = document.querySelector("dialog.plugin-modal"); assert(dialog === null || !dialog.hasAttribute("open")); });});
describe("hidePluginModal", () => { it("should close the dialog without invoking onDismiss", () => { clearDOM(); const onDismiss = mock.fn(); const pluginId = "hide.plugin"; const modalId = uniqueModalId("hide"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "p", text: "B" }, onDismiss, }); const dialog = document.querySelector("dialog.plugin-modal"); assert(dialog.hasAttribute("open")); hidePluginModal({ pluginId, modalId }); assert(!dialog.hasAttribute("open")); assert.deepEqual(onDismiss.mock.callCount(), 0); });
it("should be a no-op when no modal exists for the key", () => { clearDOM(); hidePluginModal({ pluginId: "missing.plugin", modalId: "missing-modal" }); assert(document.querySelector("dialog") === null); });
it("should be a no-op when the modal is already closed", () => { clearDOM(); const onDismiss = mock.fn(); const pluginId = "double-hide.plugin"; const modalId = uniqueModalId("double-hide"); showPluginModal({ pluginId, modalId, title: { tag: "span", text: "T" }, content: { tag: "p", text: "B" }, onDismiss, }); hidePluginModal({ pluginId, modalId }); hidePluginModal({ pluginId, modalId }); assert.deepEqual(onDismiss.mock.callCount(), 0); });});
describe("permission prompts", () => { it("describes a userFetch scope in the install prompt", async () => { const prompting = showPluginInstallPermissionsModal({ pluginName: "Alpha", permissions: { userFetch: true }, }); await waitFor(() => document.querySelector('[data-testid="permission-prompt"]'), ); const prompt = document.querySelector('[data-testid="permission-prompt"]'); // A userFetch-only manifest must not render an empty permission list assert(prompt.querySelector(".permission-prompt-section")); await respondToConfirm(false); await prompting; });
it("renders the requested origin in the fetch prompt", async () => { const prompting = showPluginFetchPermissionModal({ pluginName: "Alpha", origin: "https://api.example.com/*", }); await waitFor(() => document.querySelector('[data-testid="fetch-permission-prompt"]'), ); const prompt = document.querySelector( '[data-testid="fetch-permission-prompt"]', ); assert.equal( prompt.querySelector("code").textContent, "https://api.example.com/*", ); await respondToConfirm(false); assert.equal(await prompting, false); });});