Merge pull request #449 from PR0M3TH3AN/codex/add-integration-tests-for-video-modal

Add modal accessibility integration coverage
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-11 16:40:32 -04:00
commit ae7e12db93
4 files changed
+694 -88

No files matched your search

+6
View File
@@ -4,6 +4,12 @@ Run this checklist before publishing releases or merging changes that touch uplo
Before starting, open `docs/kitchen-sink.html` in a browser to review the modal surface reference. With that page as your visual baseline, perform this quick accessibility spot check in the running app:
Automated regression coverage now exercises these flows across both the legacy and design-system variants:
* `tests/video-modal-accessibility.test.mjs` validates sticky header transitions, scroll locking, and zap popover accessibility for the video modal.
* `tests/modal-accessibility.test.mjs` mirrors upload/edit modal accessibility along with their form validation and mode toggles.
* `tests/profile-modal-controller.test.mjs` verifies profile modal focus restoration and navigation button state changes.
* Launch the site, trigger any video card or thumbnail to open the playback modal, and keep track of the element you used to open it.
* Scroll the modal content down and back up; confirm the sticky navigation bar hides on downward scroll and reappears when you reverse direction.
* Activate the zap button to display the popover and tab through its fields to confirm focus stays within the dialog; press `Escape` to close it and ensure focus returns to the zap trigger.
+373 -81
View File
@@ -11,6 +11,11 @@ import {
openStaticModal,
closeStaticModal,
} from "../js/ui/components/staticModalAccessibility.js";
import {
setFeatureDesignSystemEnabled,
resetRuntimeFlags,
} from "../js/constants.js";
import { applyDesignSystemAttributes } from "../js/designSystem.js";
const uploadMarkupPromise = readFile(
new URL("../components/upload-modal.html", import.meta.url),
@@ -90,101 +95,388 @@ async function flushMicrotasks() {
await Promise.resolve();
}
test("UploadModal closes on Escape and restores trigger focus", async () => {
const markup = await uploadMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><button id=\"trigger\">Upload</button><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(
new Map([["components/upload-modal.html", markup]]),
),
);
for (const designSystemEnabled of [false, true]) {
const modeLabel = designSystemEnabled ? "design-system" : "legacy";
const modal = new UploadModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
});
await modal.load();
test(
`[${modeLabel}] UploadModal closes on Escape and restores trigger focus`,
async (t) => {
const markup = await uploadMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><button id=\"trigger\">Upload</button><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(new Map([["components/upload-modal.html", markup]])),
);
const trigger = document.getElementById("trigger");
assert.ok(trigger);
setFeatureDesignSystemEnabled(designSystemEnabled);
modal.open({ triggerElement: trigger });
await flushMicrotasks();
const modal = new UploadModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
});
await modal.load();
applyDesignSystemAttributes(document);
document.dispatchEvent(
new window.KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }),
);
await flushMicrotasks();
const trigger = document.getElementById("trigger");
assert.ok(trigger);
const root = modal.getRoot();
assert.ok(root?.classList.contains("hidden"));
assert.strictEqual(document.activeElement, trigger);
modal.open({ triggerElement: trigger });
await flushMicrotasks();
modal.destroy();
cleanups.reverse().forEach((fn) => fn?.());
});
document.dispatchEvent(
new window.KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
cancelable: true,
}),
);
await flushMicrotasks();
test("EditModal Escape closes and restores trigger focus", async () => {
const markup = await editMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><button id=\"trigger\">Edit</button><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(
new Map([["components/edit-video-modal.html", markup]]),
),
);
const root = modal.getRoot();
assert.ok(root?.classList.contains("hidden"));
assert.strictEqual(document.activeElement, trigger);
const modal = new EditModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
sanitizers: {},
escapeHtml: (value) => String(value ?? ""),
showError: () => {},
getMode: () => "live",
});
await modal.load();
const trigger = document.getElementById("trigger");
assert.ok(trigger);
await modal.open(
{
id: "event1",
title: "Example",
url: "https://example.com/video.mp4",
magnet: "",
ws: "",
xs: "",
enableComments: true,
isPrivate: false,
isNsfw: false,
isForKids: false,
t.after(() => {
modal.destroy();
resetRuntimeFlags();
cleanups.reverse().forEach((fn) => fn?.());
});
},
{ triggerElement: trigger },
);
await flushMicrotasks();
document.dispatchEvent(
new window.KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }),
test(
`[${modeLabel}] UploadModal backdrop click closes and restores trigger focus`,
async (t) => {
const markup = await uploadMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><button id=\"trigger\">Upload</button><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(new Map([["components/upload-modal.html", markup]])),
);
setFeatureDesignSystemEnabled(designSystemEnabled);
const modal = new UploadModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
});
await modal.load();
applyDesignSystemAttributes(document);
const trigger = document.getElementById("trigger");
assert.ok(trigger);
modal.open({ triggerElement: trigger });
await flushMicrotasks();
const backdrop = modal.getRoot()?.querySelector?.("[data-dismiss]");
assert.ok(backdrop, "expected upload modal backdrop");
backdrop.dispatchEvent(
new window.MouseEvent("click", { bubbles: true, cancelable: true }),
);
await flushMicrotasks();
const root = modal.getRoot();
assert.ok(root?.classList.contains("hidden"));
assert.strictEqual(document.activeElement, trigger);
t.after(() => {
modal.destroy();
resetRuntimeFlags();
cleanups.reverse().forEach((fn) => fn?.());
});
},
);
await flushMicrotasks();
const root = modal.getRoot();
assert.ok(root?.classList.contains("hidden"));
assert.strictEqual(document.activeElement, trigger);
test(
`[${modeLabel}] UploadModal mode toggle updates button states`,
async (t) => {
const markup = await uploadMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(new Map([["components/upload-modal.html", markup]])),
);
modal.destroy();
cleanups.reverse().forEach((fn) => fn?.());
});
setFeatureDesignSystemEnabled(designSystemEnabled);
const modal = new UploadModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
});
await modal.load();
applyDesignSystemAttributes(document);
const customButton = document.querySelector(
".upload-mode-toggle[data-upload-mode=\"custom\"]",
);
const cloudflareButton = document.querySelector(
".upload-mode-toggle[data-upload-mode=\"cloudflare\"]",
);
const customSection = document.getElementById("customUploadSection");
const cloudflareSection = document.getElementById("cloudflareUploadSection");
assert.ok(customButton && cloudflareButton);
assert.ok(customSection && cloudflareSection);
modal.setMode("cloudflare");
assert.equal(cloudflareButton.getAttribute("aria-pressed"), "true");
assert.equal(customButton.getAttribute("aria-pressed"), "false");
assert.equal(cloudflareSection.classList.contains("hidden"), false);
assert.equal(customSection.classList.contains("hidden"), true);
modal.setMode("custom");
assert.equal(customButton.getAttribute("aria-pressed"), "true");
assert.equal(cloudflareButton.getAttribute("aria-pressed"), "false");
assert.equal(customSection.classList.contains("hidden"), false);
assert.equal(cloudflareSection.classList.contains("hidden"), true);
t.after(() => {
modal.destroy();
resetRuntimeFlags();
cleanups.reverse().forEach((fn) => fn?.());
});
},
);
}
for (const designSystemEnabled of [false, true]) {
const modeLabel = designSystemEnabled ? "design-system" : "legacy";
test(
`[${modeLabel}] EditModal Escape closes and restores trigger focus`,
async (t) => {
const markup = await editMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><button id=\"trigger\">Edit</button><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(
new Map([["components/edit-video-modal.html", markup]]),
),
);
setFeatureDesignSystemEnabled(designSystemEnabled);
const modal = new EditModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
sanitizers: {},
escapeHtml: (value) => String(value ?? ""),
showError: () => {},
getMode: () => "live",
});
await modal.load();
applyDesignSystemAttributes(document);
const trigger = document.getElementById("trigger");
assert.ok(trigger);
await modal.open(
{
id: "event1",
title: "Example",
url: "https://example.com/video.mp4",
magnet: "",
ws: "",
xs: "",
enableComments: true,
isPrivate: false,
isNsfw: false,
isForKids: false,
},
{ triggerElement: trigger },
);
await flushMicrotasks();
document.dispatchEvent(
new window.KeyboardEvent("keydown", {
key: "Escape",
bubbles: true,
cancelable: true,
}),
);
await flushMicrotasks();
const root = modal.getRoot();
assert.ok(root?.classList.contains("hidden"));
assert.strictEqual(document.activeElement, trigger);
t.after(() => {
modal.destroy();
resetRuntimeFlags();
cleanups.reverse().forEach((fn) => fn?.());
});
},
);
test(
`[${modeLabel}] EditModal backdrop click closes and restores trigger focus`,
async (t) => {
const markup = await editMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><button id=\"trigger\">Edit</button><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(
new Map([["components/edit-video-modal.html", markup]]),
),
);
setFeatureDesignSystemEnabled(designSystemEnabled);
const modal = new EditModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
sanitizers: {},
escapeHtml: (value) => String(value ?? ""),
showError: () => {},
getMode: () => "live",
});
await modal.load();
applyDesignSystemAttributes(document);
const trigger = document.getElementById("trigger");
assert.ok(trigger);
await modal.open(
{
id: "event1",
title: "Example",
url: "https://example.com/video.mp4",
magnet: "",
ws: "",
xs: "",
enableComments: true,
isPrivate: false,
isNsfw: false,
isForKids: false,
},
{ triggerElement: trigger },
);
await flushMicrotasks();
const overlay = modal.overlay;
assert.ok(overlay, "expected edit modal overlay");
overlay.dispatchEvent(
new window.MouseEvent("click", { bubbles: true, cancelable: true }),
);
await flushMicrotasks();
const root = modal.getRoot();
assert.ok(root?.classList.contains("hidden"));
assert.strictEqual(document.activeElement, trigger);
t.after(() => {
modal.destroy();
resetRuntimeFlags();
cleanups.reverse().forEach((fn) => fn?.());
});
},
);
test(
`[${modeLabel}] EditModal visibility toggle updates button state`,
async (t) => {
const markup = await editMarkupPromise;
const cleanups = [];
cleanups.push(
installDom(
"<!DOCTYPE html><html><body><div id=\"modalContainer\"></div></body></html>",
),
);
cleanups.push(
stubFetch(
new Map([["components/edit-video-modal.html", markup]]),
),
);
setFeatureDesignSystemEnabled(designSystemEnabled);
const modal = new EditModal({
removeTrackingScripts: () => {},
setGlobalModalState: () => {},
sanitizers: {},
escapeHtml: (value) => String(value ?? ""),
showError: () => {},
getMode: () => "live",
});
await modal.load();
applyDesignSystemAttributes(document);
await modal.open(
{
id: "event1",
title: "Example",
url: "https://example.com/video.mp4",
magnet: "",
ws: "",
xs: "",
enableComments: true,
isPrivate: false,
isNsfw: false,
isForKids: false,
},
{},
);
await flushMicrotasks();
const privateButton = modal.visibility.buttons.find((button) =>
button?.dataset?.visibilityOption === "private",
);
const publicButton = modal.visibility.buttons.find((button) =>
button?.dataset?.visibilityOption === "public",
);
assert.ok(privateButton && publicButton);
const unlockVisibilityButton =
modal.root?.querySelector("[data-edit-target=\"editVideoIsPrivate\"]");
assert.ok(unlockVisibilityButton);
unlockVisibilityButton.dispatchEvent(
new window.Event("click", { bubbles: true, cancelable: true }),
);
modal.setVisibility("private");
assert.equal(privateButton.getAttribute("aria-pressed"), "true");
assert.equal(publicButton.getAttribute("aria-pressed"), "false");
assert.equal(modal.visibility.container?.dataset.state, "private");
modal.setVisibility("public");
assert.equal(publicButton.getAttribute("aria-pressed"), "true");
assert.equal(privateButton.getAttribute("aria-pressed"), "false");
assert.equal(modal.visibility.container?.dataset.state, "public");
t.after(() => {
modal.destroy();
resetRuntimeFlags();
cleanups.reverse().forEach((fn) => fn?.());
});
},
);
}
test("RevertModal Escape closes and restores trigger focus", async () => {
const markup = await revertMarkupPromise;
+134
View File
@@ -3,6 +3,11 @@ import { strict as assert } from 'node:assert';
import { readFile } from 'node:fs/promises';
import { JSDOM } from 'jsdom';
import ProfileModalController from '../js/ui/profileModalController.js';
import {
setFeatureDesignSystemEnabled,
resetRuntimeFlags,
} from '../js/constants.js';
import { applyDesignSystemAttributes } from '../js/designSystem.js';
const profileModalHtml = await readFile(
new URL('../components/profile-modal.html', import.meta.url),
@@ -15,6 +20,12 @@ const defaultActorHex = 'a'.repeat(64);
let dom;
let container;
async function waitForAnimationFrame(window, cycles = 1) {
for (let i = 0; i < cycles; i += 1) {
await new Promise((resolve) => window.requestAnimationFrame(() => resolve()));
}
}
beforeEach(() => {
dom = new JSDOM(
'<!DOCTYPE html><html><body><div id="modalMount"></div></body></html>',
@@ -163,6 +174,129 @@ function createController(options = {}) {
});
}
for (const designSystemEnabled of [false, true]) {
const modeLabel = designSystemEnabled ? 'design-system' : 'legacy';
test(
`[${modeLabel}] Profile modal Escape closes and restores trigger focus`,
async (t) => {
setFeatureDesignSystemEnabled(designSystemEnabled);
const controller = createController();
await controller.load();
applyDesignSystemAttributes(document);
const trigger = document.createElement('button');
trigger.id = 'profileTrigger';
trigger.textContent = 'Open profile';
document.body.appendChild(trigger);
trigger.focus();
await waitForAnimationFrame(window, 1);
const cleanup = () => {
try {
controller.hide({ silent: true });
} catch {}
try {
trigger.remove();
} catch {}
};
let cleanupRan = false;
try {
await controller.show('account');
await waitForAnimationFrame(window, 2);
const focusTrap =
controller.focusTrapContainer ||
controller.profileModalPanel ||
controller.profileModal ||
document.getElementById('profileModal');
assert.ok(focusTrap, 'focus trap container should exist');
focusTrap.dispatchEvent(
new window.KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true,
cancelable: true,
}),
);
await waitForAnimationFrame(window, 2);
const modalRoot = document.getElementById('profileModal');
assert.ok(modalRoot?.classList.contains('hidden'));
assert.strictEqual(document.activeElement, trigger);
} finally {
cleanup();
cleanupRan = true;
}
t.after(() => {
if (!cleanupRan) {
cleanup();
}
resetRuntimeFlags();
});
},
);
test(
`[${modeLabel}] Profile modal navigation buttons toggle active state`,
async (t) => {
setFeatureDesignSystemEnabled(designSystemEnabled);
const controller = createController();
await controller.load();
applyDesignSystemAttributes(document);
let cleanupRan = false;
const cleanup = () => {
try {
controller.hide({ silent: true });
} catch {}
cleanupRan = true;
};
try {
await controller.show('account');
await waitForAnimationFrame(window, 2);
const accountButton = document.getElementById('profileNavAccount');
const relaysButton = document.getElementById('profileNavRelays');
const accountPane = document.getElementById('profilePaneAccount');
const relaysPane = document.getElementById('profilePaneRelays');
assert.ok(accountButton && relaysButton);
assert.ok(accountPane && relaysPane);
controller.selectPane('relays');
await waitForAnimationFrame(window, 1);
assert.equal(relaysButton?.getAttribute('aria-selected'), 'true');
assert.equal(accountButton?.getAttribute('aria-selected'), 'false');
assert.equal(relaysPane?.classList.contains('hidden'), false);
assert.equal(accountPane?.classList.contains('hidden'), true);
controller.selectPane('account');
await waitForAnimationFrame(window, 1);
assert.equal(accountButton?.getAttribute('aria-selected'), 'true');
assert.equal(relaysButton?.getAttribute('aria-selected'), 'false');
assert.equal(accountPane?.classList.contains('hidden'), false);
assert.equal(relaysPane?.classList.contains('hidden'), true);
} finally {
cleanup();
}
t.after(() => {
if (!cleanupRan) {
cleanup();
}
resetRuntimeFlags();
});
},
);
}
test('load() injects markup and caches expected elements', async () => {
const controller = createController();
const result = await controller.load();
+181 -7
View File
@@ -4,17 +4,22 @@ import { readFile } from "node:fs/promises";
import { JSDOM } from "jsdom";
import { VideoModal } from "../js/ui/components/VideoModal.js";
import {
setFeatureDesignSystemEnabled,
resetRuntimeFlags,
} from "../js/constants.js";
import { applyDesignSystemAttributes } from "../js/designSystem.js";
const modalMarkupPromise = readFile(
new URL("../components/video-modal.html", import.meta.url),
"utf8"
);
async function setupModal() {
async function setupModal({ designSystemEnabled = false } = {}) {
const markup = await modalMarkupPromise;
const dom = new JSDOM(
`<!DOCTYPE html><html><body><button id="trigger" type="button">Open modal</button><div id="modalContainer">${markup}</div></body></html>`,
{ url: "https://example.com" }
{ url: "https://example.com", pretendToBeVisual: true }
);
const { window } = dom;
@@ -31,6 +36,11 @@ async function setupModal() {
globalThis.EventTarget = window.EventTarget;
globalThis.navigator = window.navigator;
globalThis.location = window.location;
globalThis.KeyboardEvent = window.KeyboardEvent;
globalThis.MouseEvent = window.MouseEvent;
setFeatureDesignSystemEnabled(designSystemEnabled);
applyDesignSystemAttributes(document);
const modal = new VideoModal({
removeTrackingScripts: () => {},
@@ -42,15 +52,40 @@ async function setupModal() {
const playerModal = document.querySelector("#playerModal");
assert.ok(playerModal, "player modal markup should exist");
modal.hydrate(playerModal);
applyDesignSystemAttributes(playerModal);
const trigger = document.getElementById("trigger");
assert.ok(trigger, "trigger button should exist");
return { window, document, modal, playerModal, trigger };
const cleanup = () => {
try {
resetRuntimeFlags();
} catch (error) {
console.warn("[tests] failed to reset runtime flags", error);
}
dom.window.close();
delete globalThis.window;
delete globalThis.document;
delete globalThis.HTMLElement;
delete globalThis.HTMLVideoElement;
delete globalThis.Element;
delete globalThis.CustomEvent;
delete globalThis.Event;
delete globalThis.Node;
delete globalThis.EventTarget;
delete globalThis.navigator;
delete globalThis.location;
delete globalThis.KeyboardEvent;
delete globalThis.MouseEvent;
};
return { window, document, modal, playerModal, trigger, cleanup };
}
test("backdrop data-dismiss closes the modal and restores focus", async () => {
const { window, document, modal, playerModal, trigger } = await setupModal();
test("backdrop data-dismiss closes the modal and restores focus", async (t) => {
const { window, document, modal, playerModal, trigger, cleanup } =
await setupModal();
t.after(cleanup);
const backdrop = playerModal.querySelector("[data-dismiss]");
assert.ok(backdrop, "modal backdrop should be present");
@@ -73,8 +108,10 @@ test("backdrop data-dismiss closes the modal and restores focus", async () => {
assert.strictEqual(document.activeElement, trigger);
});
test("Escape key closes the modal and returns focus to the trigger", async () => {
const { window, document, modal, playerModal, trigger } = await setupModal();
test("Escape key closes the modal and returns focus to the trigger", async (t) => {
const { window, document, modal, playerModal, trigger, cleanup } =
await setupModal();
t.after(cleanup);
const closeButton = playerModal.querySelector("#closeModal");
assert.ok(closeButton, "close button should be present");
@@ -102,3 +139,140 @@ test("Escape key closes the modal and returns focus to the trigger", async () =>
assert.equal(closeEvents, 1);
assert.strictEqual(document.activeElement, trigger);
});
for (const designSystemEnabled of [false, true]) {
const modeLabel = designSystemEnabled ? "design-system" : "legacy";
test(
`[${modeLabel}] video modal sticky navigation responds to scroll direction`,
async (t) => {
const {
window,
document,
modal,
playerModal,
trigger,
cleanup,
} = await setupModal({ designSystemEnabled });
t.after(cleanup);
modal.open(null, { triggerElement: trigger });
await Promise.resolve();
const nav = document.getElementById("modalNav");
assert.ok(nav, "navigation bar should exist");
const scrollRegion = playerModal.querySelector(".bv-modal__panel");
assert.ok(scrollRegion, "modal panel should exist");
let scrollPosition = 0;
Object.defineProperty(scrollRegion, "scrollTop", {
configurable: true,
get() {
return scrollPosition;
},
set(value) {
scrollPosition = Number(value) || 0;
}
});
scrollRegion.scrollTop = 120;
assert.equal(modal.scrollRegion.scrollTop, 120);
modal.modalNavScrollHandler?.();
assert.equal(nav.style.transform, "translateY(-100%)");
scrollRegion.scrollTop = 60;
assert.equal(modal.scrollRegion.scrollTop, 60);
modal.modalNavScrollHandler?.();
assert.equal(nav.style.transform, "translateY(0)");
scrollRegion.scrollTop = 10;
assert.equal(modal.scrollRegion.scrollTop, 10);
modal.modalNavScrollHandler?.();
assert.equal(nav.style.transform, "translateY(0)");
}
);
test(
`[${modeLabel}] video modal toggles document scroll locking on open/close`,
async (t) => {
const { document, modal, trigger, cleanup } = await setupModal({
designSystemEnabled,
});
t.after(cleanup);
modal.open(null, { triggerElement: trigger });
await Promise.resolve();
assert.equal(
document.documentElement.classList.contains("modal-open"),
true
);
assert.equal(document.body.classList.contains("modal-open"), true);
modal.close();
await Promise.resolve();
assert.equal(
document.documentElement.classList.contains("modal-open"),
false
);
assert.equal(document.body.classList.contains("modal-open"), false);
assert.strictEqual(document.activeElement, trigger);
}
);
test(
`[${modeLabel}] video modal zap dialog updates aria state while toggling`,
async (t) => {
const {
window,
document,
modal,
playerModal,
trigger,
cleanup,
} = await setupModal({ designSystemEnabled });
t.after(cleanup);
modal.open(null, { triggerElement: trigger });
await Promise.resolve();
const zapButton = document.getElementById("modalZapBtn");
const zapDialog = document.getElementById("modalZapDialog");
const amountInput = document.getElementById("modalZapAmountInput");
assert.ok(zapButton, "zap trigger should exist");
assert.ok(zapDialog, "zap dialog should exist");
assert.ok(amountInput, "zap amount input should exist");
modal.setZapVisibility(true);
assert.equal(zapDialog.hidden, true);
assert.equal(zapDialog.dataset.state, "closed");
assert.equal(zapButton.getAttribute("aria-expanded"), "false");
zapButton.dispatchEvent(
new window.MouseEvent("click", { bubbles: true, cancelable: true })
);
assert.equal(zapDialog.hidden, false);
assert.equal(zapDialog.dataset.state, "open");
assert.equal(zapDialog.getAttribute("aria-hidden"), "false");
assert.equal(zapButton.getAttribute("aria-expanded"), "true");
assert.strictEqual(document.activeElement, amountInput);
const closeButton = document.getElementById("modalZapCloseBtn");
assert.ok(closeButton, "zap close button should exist");
closeButton.dispatchEvent(
new window.MouseEvent("click", { bubbles: true, cancelable: true })
);
assert.equal(zapDialog.hidden, true);
assert.equal(zapDialog.dataset.state, "closed");
assert.equal(zapDialog.getAttribute("aria-hidden"), "true");
assert.equal(zapButton.getAttribute("aria-expanded"), "false");
modal.close();
}
);
}