From ee8569a99b066001b794e31fcaf7ba580ea95072 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:21:28 -0500 Subject: [PATCH] update --- tests/e2e/coverage-ui-runtime.spec.ts | 305 +++++++++++++++++++++ tests/e2e/coverage-utility-modules.spec.ts | 219 +++++++++++++++ 2 files changed, 524 insertions(+) create mode 100644 tests/e2e/coverage-ui-runtime.spec.ts create mode 100644 tests/e2e/coverage-utility-modules.spec.ts diff --git a/tests/e2e/coverage-ui-runtime.spec.ts b/tests/e2e/coverage-ui-runtime.spec.ts new file mode 100644 index 00000000..3b274cc6 --- /dev/null +++ b/tests/e2e/coverage-ui-runtime.spec.ts @@ -0,0 +1,305 @@ +import { expect, test } from "./helpers/instrumentedTest"; + +test.describe("runtime UI utility coverage", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/docs/kitchen-sink.html?__test__=1", { + waitUntil: "networkidle", + }); + }); + + test("exercises dynamic style scope registration and lifecycle", async ({ page }) => { + const result = await page.evaluate(async () => { + const { + registerScope, + setVariables, + releaseScope, + getScopeAttributeName, + } = await import("/js/designSystem/dynamicStyles.js"); + + const firstScope = registerScope("coverage-scope", [":scope", "& .title", "> .child"]); + const secondScope = registerScope("coverage-scope", [":scope"]); + + const firstUpdate = setVariables(firstScope, { + "--card-gap": "12px", + "--muted": "", + invalid: "skip", + }); + const secondUpdate = setVariables(firstScope, { + "--card-gap": 8, + }); + + const missingScopeUpdate = setVariables("missing-scope", { "--x": "1" }); + const releaseMissing = releaseScope("missing-scope"); + const releaseFirst = releaseScope(firstScope); + const releaseSecond = releaseScope(secondScope); + + return { + firstScope, + secondScope, + firstUpdate, + secondUpdate, + missingScopeUpdate, + releaseMissing, + releaseFirst, + releaseSecond, + scopeAttr: getScopeAttributeName(), + styleTagCount: document.querySelectorAll("style[data-ds-dynamic='true']").length, + adoptedSheetCount: Array.isArray((document as any).adoptedStyleSheets) + ? (document as any).adoptedStyleSheets.length + : 0, + }; + }); + + expect(result.firstScope).toBe("coverage-scope"); + expect(result.secondScope).toBe("coverage-scope-1"); + expect(result.firstUpdate).toBe(true); + expect(result.secondUpdate).toBe(true); + expect(result.missingScopeUpdate).toBe(false); + expect(result.releaseMissing).toBe(false); + expect(result.releaseFirst).toBe(true); + expect(result.releaseSecond).toBe(true); + expect(result.scopeAttr).toBe("data-ds-style-id"); + expect(result.styleTagCount + result.adoptedSheetCount).toBeGreaterThanOrEqual(1); + }); + + test("exercises tag preference menu rendering, actions, and state transitions", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { + createTagPreferenceMenu, + applyTagPreferenceMenuState, + TAG_PREFERENCE_ACTIONS, + } = await import("/js/ui/components/tagPreferenceMenu.js"); + + const actionEvents: Array<{ action: string; tag: string }> = []; + + const loggedOut = createTagPreferenceMenu({ + tag: "#BitVid", + isLoggedIn: false, + }); + document.body.appendChild(loggedOut!.panel); + + const loggedIn = createTagPreferenceMenu({ + tag: " nostr ", + isLoggedIn: true, + membership: { state: "interest" }, + onAction: (action: string, detail: any) => { + actionEvents.push({ action, tag: detail.normalizedTag }); + }, + }); + document.body.appendChild(loggedIn!.panel); + + loggedIn!.buttons.removeInterest.click(); + loggedIn!.buttons.addDisinterest.click(); + + applyTagPreferenceMenuState({ + buttons: loggedIn!.buttons, + membership: { state: "disinterest", interest: true, disinterest: true }, + isLoggedIn: true, + }); + + return { + actions: TAG_PREFERENCE_ACTIONS, + loggedOutTag: loggedOut!.panel.dataset.tag, + loggedOutMessage: loggedOut!.panel.textContent, + loggedOutDisabled: { + addInterest: loggedOut!.buttons.addInterest.disabled, + removeInterest: loggedOut!.buttons.removeInterest.disabled, + addDisinterest: loggedOut!.buttons.addDisinterest.disabled, + removeDisinterest: loggedOut!.buttons.removeDisinterest.disabled, + }, + loggedInTag: loggedIn!.panel.dataset.tag, + loggedInDisabledAfterTransition: { + addInterest: loggedIn!.buttons.addInterest.disabled, + removeInterest: loggedIn!.buttons.removeInterest.disabled, + addDisinterest: loggedIn!.buttons.addDisinterest.disabled, + removeDisinterest: loggedIn!.buttons.removeDisinterest.disabled, + }, + actionEvents, + }; + }); + + expect(result.actions.ADD_INTEREST).toBe("add-interest"); + expect(result.loggedOutTag).toBe("bitvid"); + expect(result.loggedInTag).toBe("nostr"); + expect(result.loggedOutMessage).toContain("Sign in to personalize"); + expect(result.loggedOutDisabled).toEqual({ + addInterest: true, + removeInterest: true, + addDisinterest: true, + removeDisinterest: true, + }); + + expect(result.actionEvents.map((entry: any) => entry.action)).toEqual([ + "remove-interest", + "add-disinterest", + ]); + expect(result.actionEvents.every((entry: any) => entry.tag === "nostr")).toBe(true); + expect(result.loggedInDisabledAfterTransition).toEqual({ + addInterest: false, + removeInterest: true, + addDisinterest: true, + removeDisinterest: false, + }); + }); + + test("exercises docs view initialization, hash routing, drawer toggle, and error handling", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + document.body.innerHTML = ` + + + +
+ `; + + (window as any).marked = { + parse(markdown: string) { + if (markdown.includes("SECOND_DOC")) { + return '

Advanced

Second doc

Advanced'; + } + return '

Intro

const x = 1;
Intro'; + }, + }; + + const highlighted: string[] = []; + (window as any).hljs = { + highlightElement(el: Element) { + highlighted.push(el.textContent || ""); + }, + }; + + const realFetch = window.fetch.bind(window); + const fetchLog: string[] = []; + window.fetch = (async (input: RequestInfo | URL) => { + const url = String(input); + fetchLog.push(url); + if (url.endsWith("content/docs/toc.json")) { + return new Response( + JSON.stringify({ + items: [ + { + slug: "intro", + title: "Intro", + path: "/content/docs/intro.md", + children: [ + { + slug: "intro-nested", + title: "Nested", + path: "/content/docs/nested.md", + }, + ], + }, + { + slug: "advanced", + title: "Advanced", + path: "/content/docs/advanced.md", + }, + ], + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ); + } + if (url.endsWith("/content/docs/intro.md")) { + return new Response("INTRO_DOC", { status: 200 }); + } + if (url.endsWith("/content/docs/advanced.md")) { + return new Response("SECOND_DOC", { status: 200 }); + } + return realFetch(input); + }) as typeof fetch; + + const { initDocsView } = await import("/js/docsView.js"); + await initDocsView(); + + const initialTitle = document.title; + const initialHash = window.location.hash; + const topLevelLinks = Array.from(document.querySelectorAll("#docsTocList [data-docs-toc-item]")).map( + (el) => (el as HTMLElement).dataset.slug, + ); + const drawerLinks = Array.from(document.querySelectorAll("#docsTocDrawerNav [data-docs-toc-item]")).map( + (el) => (el as HTMLElement).dataset.slug, + ); + + const toggle = document.querySelector("[data-docs-toc-toggle]") as HTMLButtonElement; + const drawer = document.getElementById("docsTocDrawer") as HTMLElement; + const backdrop = drawer.querySelector(".bv-modal-backdrop") as HTMLElement; + + toggle.click(); + await new Promise((resolve) => setTimeout(resolve, 0)); + const drawerOpen = drawer.getAttribute("data-open"); + const toggleExpandedOpen = toggle.getAttribute("aria-expanded"); + + backdrop.click(); + await new Promise((resolve) => setTimeout(resolve, 0)); + const drawerClosed = drawer.getAttribute("data-open"); + + window.location.hash = "#view=docs&doc=advanced"; + window.dispatchEvent(new HashChangeEvent("hashchange")); + await new Promise((resolve) => setTimeout(resolve, 0)); + await new Promise((resolve) => setTimeout(resolve, 0)); + + const advancedTitle = document.title; + const markdownHtml = + document.getElementById("markdown-container")?.innerHTML || ""; + + window.fetch = (async (input: RequestInfo | URL) => { + const url = String(input); + if (url.endsWith("content/docs/toc.json")) { + return new Response("broken", { status: 500 }); + } + return realFetch(input); + }) as typeof fetch; + + document.getElementById("docsTocList")!.innerHTML = ""; + document.getElementById("markdown-container")!.innerHTML = ""; + + await initDocsView(); + const tocErrorText = document.getElementById("docsTocList")?.textContent || ""; + const markdownErrorText = + document.getElementById("markdown-container")?.textContent || ""; + + window.fetch = realFetch; + + return { + initialTitle, + initialHash, + advancedTitle, + markdownHtml, + topLevelLinks, + drawerLinks, + drawerOpen, + drawerClosed, + toggleExpandedOpen, + fetchLog, + highlighted, + tocErrorText, + markdownErrorText, + }; + }); + + expect(result.initialTitle).toBe("Intro | bitvid docs"); + expect(result.initialHash).toContain("view=docs&doc=intro"); + expect(result.advancedTitle).toBe("Advanced | bitvid docs"); + expect(result.markdownHtml).toContain("Second doc"); + expect(result.topLevelLinks).toContain("intro"); + expect(result.topLevelLinks).toContain("advanced"); + expect(result.drawerLinks).toContain("intro"); + expect(result.drawerOpen).toBe("true"); + expect(result.drawerClosed).toBe("false"); + expect(result.toggleExpandedOpen).toBe("true"); + expect(result.fetchLog.some((url: string) => url.endsWith("content/docs/toc.json"))).toBe(true); + expect(result.fetchLog.some((url: string) => url.endsWith("/content/docs/intro.md"))).toBe(true); + expect(result.fetchLog.some((url: string) => url.endsWith("/content/docs/advanced.md"))).toBe(true); + expect(result.highlighted.length).toBeGreaterThan(0); + expect(result.tocErrorText).toContain("Unable to load docs table of contents"); + expect(result.markdownErrorText).toContain("Error loading content"); + }); +}); diff --git a/tests/e2e/coverage-utility-modules.spec.ts b/tests/e2e/coverage-utility-modules.spec.ts new file mode 100644 index 00000000..9ca3081f --- /dev/null +++ b/tests/e2e/coverage-utility-modules.spec.ts @@ -0,0 +1,219 @@ +import { expect, test } from "./helpers/instrumentedTest"; + +test.describe("runtime utility module coverage", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/docs/kitchen-sink.html?__test__=1", { + waitUntil: "networkidle", + }); + }); + + test("exercises search filter parsing and serialization", async ({ page }) => { + const result = await page.evaluate(async () => { + const { parseFilterQuery, serializeFiltersToQuery } = await import( + "/js/search/searchFilters.js" + ); + + const parsed = parseFilterQuery( + 'author:abc123 tag:#music,nostr kind:30078 relay:wss://relay.example after:2024-01-02 before:1707000000 duration:>=1.5m has:magnet nsfw:safe "exact phrase" looseTerm', + ); + + const withErrors = parseFilterQuery( + "duration:oops has:bad unknown:value before:not-a-date", + ); + + const serialized = serializeFiltersToQuery(parsed.filters); + + return { + parsed, + withErrors, + serialized, + }; + }); + + expect(result.parsed.filters.authorPubkeys).toEqual(["abc123"]); + expect(result.parsed.filters.tags).toEqual(["music", "nostr"]); + expect(result.parsed.filters.kind).toBe(30078); + expect(result.parsed.filters.relay).toBe("wss://relay.example"); + expect(result.parsed.filters.duration.minSeconds).toBe(90); + expect(result.parsed.filters.hasMagnet).toBe(true); + expect(result.parsed.filters.nsfw).toBe("false"); + expect(result.parsed.text).toContain("exact phrase"); + expect(result.parsed.text).toContain("looseTerm"); + + expect(result.withErrors.errors.length).toBeGreaterThanOrEqual(3); + expect(result.withErrors.errors.some((e: any) => /Duration/.test(e.message))).toBe(true); + expect(result.withErrors.errors.some((e: any) => /Has filter/.test(e.message))).toBe(true); + expect(result.withErrors.errors.some((e: any) => /Date value/.test(e.message))).toBe(true); + + expect(result.serialized).toContain("author:abc123"); + expect(result.serialized).toContain("tag:music"); + expect(result.serialized).toContain("kind:30078"); + expect(result.serialized).toContain("has:magnet"); + }); + + test("exercises video note payload normalization and error mapping", async ({ page }) => { + const result = await page.evaluate(async () => { + const { + normalizeVideoNotePayload, + VIDEO_NOTE_ERROR_CODES, + getVideoNoteErrorMessage, + } = await import("/js/services/videoNotePayload.js"); + + const invalid = normalizeVideoNotePayload({ + title: "", + url: "http://insecure.example/video.mp4", + }); + + const normalized = normalizeVideoNotePayload({ + legacyFormData: { + title: " Coverage Video ", + url: "https://cdn.example/video.mp4 ", + magnet: + "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567", + ws: "https://webseed.example/video.mp4", + xs: "https://cdn.example/video.torrent", + mode: "DEV", + isPrivate: true, + isNsfw: true, + isForKids: true, + storageProvider: "r2", + fileSha256: + "ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD", + originalFileSha256: + "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF", + }, + nip71: { + summary: " summary text ", + publishedAt: "2024-05-01T12:00:00Z", + duration: "120", + hashtags: ["nostr", " video "], + references: [" https://example.com/ref "], + textTracks: [ + { + url: " https://example.com/captions.vtt ", + type: " subtitles ", + language: " en ", + }, + ], + segments: [{ start: "0", end: "30", title: " Intro " }], + participants: [ + { + pubkey: + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + relay: " wss://relay.example ", + }, + ], + imeta: [ + { + m: " VIDEO/MP4 ", + url: "https://cdn.example/video.mp4", + image: [" https://cdn.example/thumb.jpg "], + duration: "98", + }, + ], + }, + }); + + return { + invalid, + normalized, + invalidUrlCode: VIDEO_NOTE_ERROR_CODES.INVALID_URL_PROTOCOL, + invalidUrlMessage: getVideoNoteErrorMessage( + VIDEO_NOTE_ERROR_CODES.INVALID_URL_PROTOCOL, + ), + fallbackMessage: getVideoNoteErrorMessage("unknown"), + }; + }); + + expect(result.invalid.errors).toContain(result.invalidUrlCode); + expect(result.invalid.errors).toContain("missing_title"); + expect(result.invalidUrlMessage).toMatch(/HTTPS/i); + expect(result.fallbackMessage).toMatch(/Unable to submit video/i); + + const legacy = result.normalized.payload.legacyFormData; + expect(legacy.title).toBe("Coverage Video"); + expect(legacy.mode).toBe("dev"); + expect(legacy.isPrivate).toBe(true); + expect(legacy.isNsfw).toBe(true); + expect(legacy.isForKids).toBe(false); + expect(legacy.storagePointer).toContain("r2:"); + expect(legacy.fileSha256).toHaveLength(64); + expect(legacy.originalFileSha256).toHaveLength(64); + expect(legacy.magnet).toContain("magnet:?xt=urn:btih:"); + expect(legacy.ws).toContain("https://webseed.example/video.mp4"); + expect(legacy.xs).toContain("https://cdn.example/video.torrent"); + + const nip71 = result.normalized.payload.nip71; + expect(nip71.summary).toBe("summary text"); + expect(nip71.duration).toBe(120); + expect(nip71.imeta[0].m).toBe("video/mp4"); + expect(nip71.hashtags).toEqual(["nostr", "video"]); + expect(nip71.segments[0].start).toBe(0); + expect(nip71.participants[0].relay).toBe("wss://relay.example"); + }); + + test("exercises attachment tag parsing and attachment descriptions", async ({ + page, + }) => { + const result = await page.evaluate(async () => { + const { + ATTACHMENT_KIND, + parseAttachmentTags, + extractAttachmentsFromMessage, + formatAttachmentSize, + describeAttachment, + } = await import("/js/attachments/attachmentUtils.js"); + + const parsed = parseAttachmentTags([ + ["x", "A".repeat(64)], + ["url", "https://cdn.example/file.bin"], + ["name", "manual.pdf"], + ["type", "application/pdf"], + ["size", "2048"], + ["k", "secret"], + ]); + + const noSource = parseAttachmentTags([["name", "nameless"]]); + const extracted = extractAttachmentsFromMessage({ + message: { + kind: ATTACHMENT_KIND, + tags: [["x", "b".repeat(64)], ["url", "https://cdn.example/second.bin"]], + }, + }); + const skipped = extractAttachmentsFromMessage({ + event: { kind: 1, tags: [["x", "c".repeat(64)]] }, + }); + + return { + parsed, + noSource, + extractedLength: extracted.length, + extractedHash: extracted[0]?.x, + skippedLength: skipped.length, + sizeBytes: formatAttachmentSize(999), + sizeKilobytes: formatAttachmentSize(2048), + sizeMegabytes: formatAttachmentSize(1048576), + describedName: describeAttachment(parsed), + describedType: describeAttachment({ type: "image/png" }), + describedFallback: describeAttachment(null), + }; + }); + + expect(result.parsed.x).toBe("a".repeat(64)); + expect(result.parsed.url).toBe("https://cdn.example/file.bin"); + expect(result.parsed.size).toBe(2048); + expect(result.parsed.encrypted).toBe(true); + expect(result.noSource).toBeNull(); + + expect(result.extractedLength).toBe(1); + expect(result.extractedHash).toBe("b".repeat(64)); + expect(result.skippedLength).toBe(0); + + expect(result.sizeBytes).toBe("999 B"); + expect(result.sizeKilobytes).toBe("2.0 KB"); + expect(result.sizeMegabytes).toBe("1.0 MB"); + expect(result.describedName).toBe("Attachment: manual.pdf"); + expect(result.describedType).toBe("Attachment (image/png)"); + expect(result.describedFallback).toBe("Attachment"); + }); +});