mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 09:22:24 +00:00
update
This commit is contained in:
1 parent
8adaee87db
commit
ee8569a99b
2 files changed
+524
No files matched your search
@@ -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 = `
|
||||
<button data-docs-toc-toggle type="button" aria-expanded="false">Menu</button>
|
||||
<nav id="docsTocList"></nav>
|
||||
<div id="docsTocDrawer" class="hidden" data-open="false">
|
||||
<div class="bv-modal-backdrop"></div>
|
||||
<div data-docs-toc-panel tabindex="-1">
|
||||
<nav id="docsTocDrawerNav"></nav>
|
||||
</div>
|
||||
</div>
|
||||
<article id="markdown-container"></article>
|
||||
`;
|
||||
|
||||
(window as any).marked = {
|
||||
parse(markdown: string) {
|
||||
if (markdown.includes("SECOND_DOC")) {
|
||||
return '<h2 id="advanced">Advanced</h2><p>Second doc</p><a data-docs-toc-item href="#advanced">Advanced</a>';
|
||||
}
|
||||
return '<h2 id="intro">Intro</h2><pre><code>const x = 1;</code></pre><a data-docs-toc-item href="#intro">Intro</a>';
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user