From 218159701a8064c0d3dcdc226f9a27e03485f683 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:48:57 +0000 Subject: [PATCH] fix(ci): restore firefox and prevent dom crashes - Fixed `package.json` to restrict `test:visual` to `visual` project, preventing execution of E2E tests in the `build` job (which lacks firefox). - Fixed `js/ui/components/UploadModal.js` and `js/ui/components/SearchFilterModal.js` with defensive null checks to prevent "Cannot set properties of null" crashes during E2E tests. - Verified `ci.yml` installs firefox for `e2e-headless`. - Verified `js/relayManager.js` handles missing `window.location`. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/ui/components/SearchFilterModal.js | 112 ++++++++++++++------------ js/ui/components/UploadModal.js | 30 +++---- package.json | 4 +- 3 files changed, 77 insertions(+), 69 deletions(-) diff --git a/js/ui/components/SearchFilterModal.js b/js/ui/components/SearchFilterModal.js index 570eccc8..5320a072 100644 --- a/js/ui/components/SearchFilterModal.js +++ b/js/ui/components/SearchFilterModal.js @@ -197,8 +197,8 @@ const formatDateInputValue = (timestampSeconds) => { function handleDateChip(days) { if (days === "all") { - dateStartInput.value = ""; - dateEndInput.value = ""; + if (dateStartInput) dateStartInput.value = ""; + if (dateEndInput) dateEndInput.value = ""; return; } @@ -209,8 +209,8 @@ function handleDateChip(days) { const start = new Date(); start.setDate(now.getDate() - numDays); - dateStartInput.value = formatDateInputValue(Math.floor(start.getTime() / 1000)); - dateEndInput.value = formatDateInputValue(Math.floor(now.getTime() / 1000)); + if (dateStartInput) dateStartInput.value = formatDateInputValue(Math.floor(start.getTime() / 1000)); + if (dateEndInput) dateEndInput.value = formatDateInputValue(Math.floor(now.getTime() / 1000)); } // Helpers for Chips @@ -234,49 +234,53 @@ function syncStateToControls(filters) { const safeFilters = filters || DEFAULT_FILTERS; // Date - dateStartInput.value = formatDateInputValue(safeFilters.dateRange?.after); - dateEndInput.value = formatDateInputValue(safeFilters.dateRange?.before); + if (dateStartInput) dateStartInput.value = formatDateInputValue(safeFilters.dateRange?.after); + if (dateEndInput) dateEndInput.value = formatDateInputValue(safeFilters.dateRange?.before); // Logic to highlight correct date chip if it matches // (Simplified: just highlight 'All Time' if no dates, otherwise clear chips) const hasDateRange = Number.isFinite(safeFilters.dateRange?.after) || Number.isFinite(safeFilters.dateRange?.before); - const dateBtns = dateChipsContainer.querySelectorAll("button"); - dateBtns.forEach(btn => { - if (btn.dataset.days === "all") { - const isActive = !hasDateRange; - btn.setAttribute("aria-pressed", isActive); - btn.dataset.state = isActive ? "on" : "off"; - } else { - // We could try to calculate if range matches 24h/7d etc, but for now just clear them if custom dates are set - btn.setAttribute("aria-pressed", "false"); - btn.dataset.state = "off"; - } - }); + if (dateChipsContainer) { + const dateBtns = dateChipsContainer.querySelectorAll("button"); + dateBtns.forEach(btn => { + if (btn.dataset.days === "all") { + const isActive = !hasDateRange; + btn.setAttribute("aria-pressed", isActive); + btn.dataset.state = isActive ? "on" : "off"; + } else { + // We could try to calculate if range matches 24h/7d etc, but for now just clear them if custom dates are set + btn.setAttribute("aria-pressed", "false"); + btn.dataset.state = "off"; + } + }); + } // Sort - sortSelect.value = safeFilters.sort || "relevance"; + if (sortSelect) sortSelect.value = safeFilters.sort || "relevance"; // Author - authorInput.value = safeFilters.authorPubkeys?.join(", ") || ""; + if (authorInput) authorInput.value = safeFilters.authorPubkeys?.join(", ") || ""; // Tags - tagsInput.value = ""; + if (tagsInput) tagsInput.value = ""; // We separate tags that are in chips vs typed const activeTags = new Set(safeFilters.tags || []); - const tagBtns = tagChipsContainer.querySelectorAll("button"); const chippedTags = new Set(); - tagBtns.forEach(btn => { - const tag = btn.dataset.tag; - chippedTags.add(tag); - const isActive = activeTags.has(tag); - btn.setAttribute("aria-pressed", isActive); - btn.dataset.state = isActive ? "on" : "off"; - }); + if (tagChipsContainer) { + const tagBtns = tagChipsContainer.querySelectorAll("button"); + tagBtns.forEach(btn => { + const tag = btn.dataset.tag; + chippedTags.add(tag); + const isActive = activeTags.has(tag); + btn.setAttribute("aria-pressed", isActive); + btn.dataset.state = isActive ? "on" : "off"; + }); + } // Remaining tags go to input const otherTags = (safeFilters.tags || []).filter(t => !chippedTags.has(t)); - if (otherTags.length) { + if (otherTags.length && tagsInput) { tagsInput.value = otherTags.join(", "); } @@ -284,34 +288,38 @@ function syncStateToControls(filters) { const minSec = safeFilters.duration?.minSeconds; const maxSec = safeFilters.duration?.maxSeconds; - durationShortCheckbox.checked = false; - durationMediumCheckbox.checked = false; - durationLongCheckbox.checked = false; + if (durationShortCheckbox) durationShortCheckbox.checked = false; + if (durationMediumCheckbox) durationMediumCheckbox.checked = false; + if (durationLongCheckbox) durationLongCheckbox.checked = false; - if (minSec === null && maxSec === 300) durationShortCheckbox.checked = true; - if (minSec === 300 && maxSec === 1200) durationMediumCheckbox.checked = true; - if (minSec === 1200 && maxSec === null) durationLongCheckbox.checked = true; + if (minSec === null && maxSec === 300 && durationShortCheckbox) durationShortCheckbox.checked = true; + if (minSec === 300 && maxSec === 1200 && durationMediumCheckbox) durationMediumCheckbox.checked = true; + if (minSec === 1200 && maxSec === null && durationLongCheckbox) durationLongCheckbox.checked = true; // Has - const hasBtns = hasChipsContainer.querySelectorAll("button"); - hasBtns.forEach(btn => { - const type = btn.dataset.has; - let isActive = false; - if (type === "magnet") isActive = safeFilters.hasMagnet === true; - if (type === "url") isActive = safeFilters.hasUrl === true; + if (hasChipsContainer) { + const hasBtns = hasChipsContainer.querySelectorAll("button"); + hasBtns.forEach(btn => { + const type = btn.dataset.has; + let isActive = false; + if (type === "magnet") isActive = safeFilters.hasMagnet === true; + if (type === "url") isActive = safeFilters.hasUrl === true; - btn.setAttribute("aria-pressed", isActive); - btn.dataset.state = isActive ? "on" : "off"; - }); + btn.setAttribute("aria-pressed", isActive); + btn.dataset.state = isActive ? "on" : "off"; + }); + } // NSFW - if (ALLOW_NSFW_CONTENT) { - const nsfwIsOn = safeFilters.nsfw === "true" || safeFilters.nsfw === "only"; - nsfwToggle.setAttribute("aria-checked", nsfwIsOn); - nsfwToggle.classList.toggle("is-on", nsfwIsOn); - } else { - nsfwToggle.setAttribute("aria-checked", "false"); - nsfwToggle.classList.remove("is-on"); + if (nsfwToggle) { + if (ALLOW_NSFW_CONTENT) { + const nsfwIsOn = safeFilters.nsfw === "true" || safeFilters.nsfw === "only"; + nsfwToggle.setAttribute("aria-checked", nsfwIsOn); + nsfwToggle.classList.toggle("is-on", nsfwIsOn); + } else { + nsfwToggle.setAttribute("aria-checked", "false"); + nsfwToggle.classList.remove("is-on"); + } } // Auto-expand Advanced if needed diff --git a/js/ui/components/UploadModal.js b/js/ui/components/UploadModal.js index 40f446d5..2c6920ad 100644 --- a/js/ui/components/UploadModal.js +++ b/js/ui/components/UploadModal.js @@ -517,9 +517,9 @@ export class UploadModal { // Show UI this.sourceSections.progress.classList.remove("hidden"); this.sourceSections.results.classList.remove("hidden"); - this.results.videoUrl.value = "Uploading..."; - this.results.magnet.value = "Pending..."; - this.results.torrentUrl.value = "Pending..."; + if (this.results.videoUrl) this.results.videoUrl.value = "Uploading..."; + if (this.results.magnet) this.results.magnet.value = "Pending..."; + if (this.results.torrentUrl) this.results.torrentUrl.value = "Pending..."; this.updateVideoProgress(0, "Preparing upload..."); @@ -615,7 +615,7 @@ export class UploadModal { provider: providerLabel, prefix: storagePrefix, }); - this.results.videoUrl.value = videoPublicUrl; + if (this.results.videoUrl) this.results.videoUrl.value = videoPublicUrl; this.updateVideoProgress(1, "Video uploaded."); // 5. Handle Torrent Result & Upload .torrent file @@ -670,15 +670,15 @@ export class UploadModal { this.torrentState.magnet = magnet; - this.results.magnet.value = magnet; - this.results.torrentUrl.value = torrentPublicUrl; + if (this.results.magnet) this.results.magnet.value = magnet; + if (this.results.torrentUrl) this.results.torrentUrl.value = torrentPublicUrl; this.updateVideoProgress(1, "Ready to publish!"); } else { this.torrentState.status = 'skipped'; // Failed hash or invalid this.updateVideoProgress(1, "Upload complete (No torrent fallback)."); - this.results.magnet.value = "Not available (Info Hash failed)"; - this.results.torrentUrl.value = "Not available"; + if (this.results.magnet) this.results.magnet.value = "Not available (Info Hash failed)"; + if (this.results.torrentUrl) this.results.torrentUrl.value = "Not available"; } } catch (err) { @@ -699,7 +699,7 @@ export class UploadModal { alert(`Upload failed: ${err.message}`); - this.inputs.file.value = ""; // Reset + if (this.inputs.file) this.inputs.file.value = ""; // Reset } } @@ -782,7 +782,7 @@ export class UploadModal { this.thumbnailUploadState.status = 'complete'; this.thumbnailUploadState.url = publicUrl; - this.inputs.thumbnail.value = publicUrl; + if (this.inputs.thumbnail) this.inputs.thumbnail.value = publicUrl; this.updateThumbnailProgress(1, "Thumbnail uploaded."); @@ -813,18 +813,18 @@ export class UploadModal { } const pct = Math.round(fraction * 100); - this.inputs.progress.value = pct; - this.statusText.uploadPercent.textContent = `${pct}%`; + if (this.inputs.progress) this.inputs.progress.value = pct; + if (this.statusText.uploadPercent) this.statusText.uploadPercent.textContent = `${pct}%`; } updateThumbnailProgress(fraction, text) { - if (text) this.statusText.thumbnailMain.textContent = text; + if (text && this.statusText.thumbnailMain) this.statusText.thumbnailMain.textContent = text; if (fraction === null) return; const pct = Math.round(fraction * 100); - this.inputs.thumbnailProgress.value = pct; - this.statusText.thumbnailPercent.textContent = `${pct}%`; + if (this.inputs.thumbnailProgress) this.inputs.thumbnailProgress.value = pct; + if (this.statusText.thumbnailPercent) this.statusText.thumbnailPercent.textContent = `${pct}%`; } diff --git a/package.json b/package.json index 335c5317..3204275c 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "lint:tailwind-brackets": "node scripts/check-design-tokens.mjs --check=brackets", "lint:tailwind-colors": "node scripts/check-tailwind-colors.mjs", "check:css": "npm run build:css", - "test:visual": "npm run build && npx playwright test", - "test:visual:update": "npm run build && cross-env UPDATE_VISUAL_BASELINES=1 npx playwright test", + "test:visual": "npm run build && npx playwright test --project=visual", + "test:visual:update": "npm run build && cross-env UPDATE_VISUAL_BASELINES=1 npx playwright test --project=visual", "test:e2e": "npm run build && npx playwright test tests/e2e --project=e2e --project=e2e-firefox", "test:e2e:chromium": "npm run build && npx playwright test tests/e2e --project=e2e", "test:e2e:firefox": "npm run build && npx playwright test tests/e2e --project=e2e-firefox",