From c0343644a5cd557a9d98b7f31c41fabf00bc7ce8 Mon Sep 17 00:00:00 2001 From: shawn Date: Tue, 13 Oct 2026 10:02:45 +0800 Subject: [PATCH 1/2] fix: restore commit stats layout after GitHub DOM changes --- src/features/commit-diff-stats.test.ts | 26 ++++++++++++++++++++++++++ src/lib/commit-dom.ts | 5 +++-- src/lib/info-row-skeleton.test.ts | 21 +++++++++++++++++++++ src/styles/skeleton-reserve.css | 4 ++-- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/features/commit-diff-stats.test.ts b/src/features/commit-diff-stats.test.ts index a4fbe2b..14aeae7 100644 --- a/src/features/commit-diff-stats.test.ts +++ b/src/features/commit-diff-stats.test.ts @@ -77,6 +77,32 @@ describe("injectCommitDiffStats", () => { expect(badge.title).toBe("10 additions, 2 deletions"); }); + it("keeps stats below the metadata in GitHub's grid rows with unprefixed CSS classes", async () => { + document.body.innerHTML = ` +
  • + +
    +
    author and checks
    +
    + +
  • + `; + vi.mocked(fetchCommitDiffStats).mockResolvedValue([ + { sha: SHA_A, additions: 24, deletions: 2, changedFiles: 5 }, + ]); + + await injectCommitDiffStats(); + await injectCommitDiffStats(); + + const badge = document.querySelector(BADGE)!; + expect(badge.parentElement).toBe(document.querySelector(".inner__Rm1Enx")); + expect(badge.previousElementSibling).toBe(document.getElementById("metadata")); + expect(document.querySelectorAll(BADGE)).toHaveLength(1); + expect(document.querySelector(`#row-a > ${BADGE}`)).toBeNull(); + }); + it("leaves rows whose SHA the API did not return without a badge", async () => { document.body.innerHTML = commitRow("row-a", SHA_A) + commitRow("row-b", SHA_B); vi.mocked(fetchCommitDiffStats).mockResolvedValue([ diff --git a/src/lib/commit-dom.ts b/src/lib/commit-dom.ts index 137d9cc..d84c709 100644 --- a/src/lib/commit-dom.ts +++ b/src/lib/commit-dom.ts @@ -3,8 +3,9 @@ // Covers both classic GitHub DOM (
  • ) and new React DOM (div.TimelineItem-body / div[data-testid]). export const COMMIT_ROW_SELECTOR = "li, div.TimelineItem-body, div[data-testid]"; -// Hashed CSS-module class on new GitHub's commit-row inner content wrapper. -export const MAIN_CONTENT_INNER_SELECTOR = "[class*='MainContent-module__inner']"; +// GitHub's commit content wrapper now omits the CSS-module name prefix. +export const MAIN_CONTENT_INNER_SELECTOR = + "[class*='MainContent-module__inner'], [class^='inner__']"; export function collectCommitRows(owner: string, repo: string): Map { const pattern = new RegExp(`^/${owner}/${repo}/commit/([0-9a-f]{40})$`, "i"); diff --git a/src/lib/info-row-skeleton.test.ts b/src/lib/info-row-skeleton.test.ts index c6c0b93..bf90f03 100644 --- a/src/lib/info-row-skeleton.test.ts +++ b/src/lib/info-row-skeleton.test.ts @@ -100,4 +100,25 @@ describe("reserveInfoRowSkeletons", () => { vi.useRealTimers(); } }); + + it("reserves commit stats inside the unprefixed content wrapper instead of the grid row", () => { + setUrl(`${GH}/owner/repo/commits/main`); + document.body.innerHTML = ` +
  • +

    commit

    +
    author and checks
    +
  • + `; + + reserveInfoRowSkeletons({ "feature-commit-diff-stats": true }); + reserveInfoRowSkeletons({ "feature-commit-diff-stats": true }); + + const skeleton = document.querySelector(".bg-skeleton-pill--commit-diff")!; + expect(skeleton.parentElement).toBe(document.querySelector(".inner__Rm1Enx")); + expect(skeleton.previousElementSibling).toBe(document.getElementById("metadata")); + expect(document.querySelectorAll(".bg-skeleton-pill--commit-diff")).toHaveLength(1); + expect(document.querySelector("#row > .bg-skeleton-pill--commit-diff")).toBeNull(); + clearSkeletons("commitDiff"); + expect(document.querySelector(".bg-skeleton-pill--commit-diff")).toBeNull(); + }); }); diff --git a/src/styles/skeleton-reserve.css b/src/styles/skeleton-reserve.css index 31a41ae..c5de8f7 100644 --- a/src/styles/skeleton-reserve.css +++ b/src/styles/skeleton-reserve.css @@ -20,7 +20,7 @@ [class*="MainContent-module__inner"]::after, [data-bg-page="commits-list"] li[data-testid="commit-row-item"] - [class*="MainContent-module__inner"]::after { + :is([class*="MainContent-module__inner"], [class^="inner__"])::after { content: ""; display: block; height: 20px; @@ -41,7 +41,7 @@ [class*="MainContent-module__inner"]:has(.better-github-info-row)::after, [data-bg-page="commits-list"] li[data-testid="commit-row-item"] - [class*="MainContent-module__inner"]:has( + :is([class*="MainContent-module__inner"], [class^="inner__"]):has( .better-github-commit-diff-stats, .bg-skeleton-pill--commit-diff )::after { From 920dae4d5f65daf2a2f2d260725746616e4ff8d7 Mon Sep 17 00:00:00 2001 From: shawn Date: Tue, 13 Oct 2026 10:39:59 +0800 Subject: [PATCH 2/2] fix: adapt PR enhancements to updated GitHub DOM --- src/features/commit-diff-stats.ts | 3 +- src/features/commit-tags.ts | 3 +- src/features/pr-collapse-expand.test.ts | 130 +++++++++++++++++++++++- src/features/pr-collapse-expand.ts | 55 ++++++---- src/features/pr-dashboard.test.ts | 8 +- src/lib/commit-dom.ts | 4 - src/lib/info-row-skeleton.test.ts | 2 +- src/lib/info-row-skeleton.ts | 3 +- src/lib/info-row.test.ts | 15 +++ src/lib/info-row.ts | 3 +- src/lib/list-view-dom.ts | 4 + src/styles/skeleton-reserve.css | 32 ++++-- src/test-utils/pr-dashboard.ts | 21 +++- 13 files changed, 244 insertions(+), 39 deletions(-) create mode 100644 src/lib/list-view-dom.ts diff --git a/src/features/commit-diff-stats.ts b/src/features/commit-diff-stats.ts index ffc8ee7..093a688 100644 --- a/src/features/commit-diff-stats.ts +++ b/src/features/commit-diff-stats.ts @@ -1,6 +1,7 @@ import { isCommitsListPage, getRepoInfo } from "../lib/page-detect"; import { fetchCommitDiffStats } from "../lib/github-api"; -import { collectCommitRows, MAIN_CONTENT_INNER_SELECTOR } from "../lib/commit-dom"; +import { collectCommitRows } from "../lib/commit-dom"; +import { MAIN_CONTENT_INNER_SELECTOR } from "../lib/list-view-dom"; import { buildDiffStatsBadge } from "../lib/diff-stats-badge"; import { clearSkeletons } from "../lib/info-row-skeleton"; diff --git a/src/features/commit-tags.ts b/src/features/commit-tags.ts index 4c7d5d5..3eda343 100644 --- a/src/features/commit-tags.ts +++ b/src/features/commit-tags.ts @@ -1,7 +1,8 @@ import { isCommitsListPage, getRepoInfo } from "../lib/page-detect"; import { fetchRepoTags } from "../lib/github-api"; import { escapeHtml } from "../lib/utils"; -import { collectCommitRows, MAIN_CONTENT_INNER_SELECTOR } from "../lib/commit-dom"; +import { collectCommitRows } from "../lib/commit-dom"; +import { MAIN_CONTENT_INNER_SELECTOR } from "../lib/list-view-dom"; import { t } from "../lib/i18n"; const TAG_CLASS = "better-github-commit-tag"; diff --git a/src/features/pr-collapse-expand.test.ts b/src/features/pr-collapse-expand.test.ts index 120ce05..098c131 100644 --- a/src/features/pr-collapse-expand.test.ts +++ b/src/features/pr-collapse-expand.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { setUrl } from "../test-utils/url"; import { injectPRCollapseExpand } from "./pr-collapse-expand"; @@ -9,6 +9,134 @@ describe("injectPRCollapseExpand", () => { document.body.innerHTML = ""; }); + afterEach(() => { + vi.useRealTimers(); + }); + + it.each(["prefixed", "current"])("toggles React files and folders with %s CSS classes", (dom) => { + setUrl(`${GH}/owner/repo/pull/42/changes`); + const prefix = dom === "prefixed" ? "DiffFileHeader-module__" : ""; + const sidebar = + dom === "prefixed" + ? 'class="PullRequestFileTree-module__sidebar__abc"' + : 'id="pr-file-tree" class="sidebar__abc"'; + document.body.innerHTML = ` +
    +
      +
    • +
      +
    • +
    • +
      +
    • +
    +
    +
    +
    +
    +
    +
    + `; + const folderOrder: string[] = []; + document.querySelectorAll("[class*='TreeViewItemContent']").forEach((toggle) => { + toggle.addEventListener("click", () => { + const folder = toggle.closest("li")!; + folderOrder.push(folder.id); + folder.setAttribute( + "aria-expanded", + String(folder.getAttribute("aria-expanded") !== "true"), + ); + }); + }); + document.querySelectorAll("[id^='file-']").forEach((toggle) => { + toggle.addEventListener("click", () => + toggle + .closest("[class*='diff-file-header']")! + .classList.toggle(`${prefix}diff-file-header-collapsed__abc`), + ); + }); + + vi.useFakeTimers(); + injectPRCollapseExpand(); + injectPRCollapseExpand(); + const treeToggle = document.querySelector(".better-github-toggle-tree")!; + const diffToggle = document.querySelector(".better-github-collapse-expand")!; + expect(treeToggle.nextElementSibling?.className).toContain("FileTreeScrollable"); + expect(diffToggle.nextElementSibling?.id).toBe("review"); + expect(document.querySelectorAll(".better-github-toggle-tree")).toHaveLength(1); + expect(document.querySelectorAll(".better-github-collapse-expand")).toHaveLength(1); + + treeToggle.click(); + diffToggle.click(); + vi.advanceTimersByTime(100); + expect(folderOrder).toEqual(["child", "parent"]); + expect( + [...document.querySelectorAll("[aria-expanded]")].map((e) => e.getAttribute("aria-expanded")), + ).toEqual(["false", "false"]); + expect(document.querySelectorAll("[class*='diff-file-header-collapsed']")).toHaveLength(2); + + treeToggle.click(); + diffToggle.click(); + vi.advanceTimersByTime(100); + expect(folderOrder.slice(2)).toEqual(["parent", "child"]); + expect(document.querySelectorAll("[class*='diff-file-header-collapsed']")).toHaveLength(0); + }); + + it("expands child folders that React mounts after their parent opens", () => { + setUrl(`${GH}/owner/repo/pull/42/changes`); + document.body.innerHTML = ` +
    + `; + const expanded = new Set(); + const order: string[] = []; + const names = ["parent", "child", "grandchild"]; + const scrollable = document.querySelector("[class*='FileTreeScrollable']")!; + function folder(level: number): string { + const name = names[level]; + const children = + expanded.has(name) && level + 1 < names.length + ? `
      ${folder(level + 1)}
    ` + : ""; + return `
  • +
    ${children} +
  • `; + } + function renderTree(): void { + scrollable.innerHTML = `
      ${folder(0)}
    `; + scrollable + .querySelectorAll("[class*='TreeViewItemContent']") + .forEach((toggle) => { + toggle.addEventListener("click", () => { + const name = toggle.closest("li")!.id; + order.push(name); + if (expanded.has(name)) expanded.delete(name); + else expanded.add(name); + renderTree(); + }); + }); + } + renderTree(); + vi.useFakeTimers(); + injectPRCollapseExpand(); + const toggle = document.querySelector(".better-github-toggle-tree")!; + + toggle.click(); + vi.advanceTimersByTime(100); + expect(order).toEqual(names); + expect([...expanded]).toEqual(names); + expect(scrollable.querySelectorAll('[aria-expanded="true"]')).toHaveLength(3); + + toggle.click(); + vi.advanceTimersByTime(100); + expect(order.slice(3)).toEqual(["grandchild", "child", "parent"]); + expect(expanded.size).toBe(0); + expect(scrollable.querySelectorAll("[role='treeitem']")).toHaveLength(1); + + toggle.click(); + vi.advanceTimersByTime(100); + expect([...expanded]).toEqual(names); + }); + it("injects one diff toggle and toggles classic diff files", () => { setUrl(`${GH}/owner/repo/commit/abc123`); document.body.innerHTML = ` diff --git a/src/features/pr-collapse-expand.ts b/src/features/pr-collapse-expand.ts index a618f44..8759b29 100644 --- a/src/features/pr-collapse-expand.ts +++ b/src/features/pr-collapse-expand.ts @@ -3,6 +3,7 @@ import { t } from "../lib/i18n"; const TREE_BTN_CLASS = "better-github-toggle-tree"; const DIFF_BTN_CLASS = "better-github-collapse-expand"; +const TREE_SIDEBAR_SELECTOR = "#pr-file-tree, [class*='PullRequestFileTree-module__sidebar']"; // 16x16 Octicon SVG paths const ICON_FOLD_DOWN = @@ -24,6 +25,7 @@ export function injectPRCollapseExpand(): void { // --- File tree folder collapse/expand (PR pages only) --- interface TreeFolder { + element: Element; isExpanded: boolean; level: number; toggle(): void; @@ -31,13 +33,12 @@ interface TreeFolder { function getTreeFolders(): TreeFolder[] { // New experience: aria-expanded lives on the
  • itself - const newSidebar = document.querySelector( - '[class*="PullRequestFileTree-module__sidebar"]' - ); + const newSidebar = document.querySelector(TREE_SIDEBAR_SELECTOR); if (newSidebar) { return Array.from( newSidebar.querySelectorAll('li[role="treeitem"][aria-expanded]') ).map((el) => ({ + element: el, isExpanded: el.getAttribute("aria-expanded") === "true", level: parseInt(el.getAttribute("aria-level") || "0", 10), toggle: () => @@ -55,6 +56,7 @@ function getTreeFolders(): TreeFolder[] { "button.ActionList-content[aria-expanded]" ); return { + element: el, isExpanded: btn?.getAttribute("aria-expanded") === "true", level: parseInt(el.getAttribute("aria-level") || "0", 10), toggle: () => btn?.click(), @@ -67,9 +69,7 @@ function findTreeInjectionPoint(): { reference: Element; } | null { // New experience - const newSidebar = document.querySelector( - '[class*="PullRequestFileTree-module__sidebar"]' - ); + const newSidebar = document.querySelector(TREE_SIDEBAR_SELECTOR); if (newSidebar) { const scrollable = newSidebar.querySelector( '[class*="FileTreeScrollable"]' @@ -119,23 +119,44 @@ function injectTreeToggle(): void { updateLabel(); + let expansionFrame: number | null = null; btn.addEventListener("click", () => { // Release focus so Space keeps scrolling the PR instead of re-triggering the button. btn.blur(); + if (expansionFrame !== null) cancelAnimationFrame(expansionFrame); + expansionFrame = null; const folders = getTreeFolders(); const shouldCollapse = areMajorityExpanded(); - // Sort by depth: collapse deepest first, expand shallowest first - const sorted = [...folders].sort((a, b) => - shouldCollapse ? b.level - a.level : a.level - b.level - ); + if (!shouldCollapse) { + // React unmounts children of collapsed folders. Expand one depth per frame + // so the next pass sees the newly rendered child folders. + const attempted = new WeakSet(); + const expandNextLevel = () => { + expansionFrame = null; + if (!btn.isConnected) return; + const collapsed = getTreeFolders().filter( + (folder) => !folder.isExpanded && !attempted.has(folder.element) + ); + if (collapsed.length === 0) { + updateLabel(); + return; + } + const level = Math.min(...collapsed.map((folder) => folder.level)); + for (const folder of collapsed.filter((folder) => folder.level === level)) { + attempted.add(folder.element); + folder.toggle(); + } + expansionFrame = requestAnimationFrame(expandNextLevel); + }; + expandNextLevel(); + return; + } + // Collapse deepest first, before React unmounts child folders. + const sorted = [...folders].sort((a, b) => b.level - a.level); for (const folder of sorted) { - if (shouldCollapse && folder.isExpanded) { - folder.toggle(); - } else if (!shouldCollapse && !folder.isExpanded) { - folder.toggle(); - } + if (folder.isExpanded) folder.toggle(); } setTimeout(updateLabel, 100); @@ -155,7 +176,7 @@ interface DiffFile { function getDiffFiles(): DiffFile[] { // New experience const newHeaders = document.querySelectorAll( - '[class*="DiffFileHeader-module__diff-file-header"]' + '[class*="diff-file-header"]' ); if (newHeaders.length > 0) { return Array.from(newHeaders).map((header) => ({ @@ -184,7 +205,7 @@ function findDiffInjectionPoint(): { } | null { // New experience: prepend inside the right controls area const prToolbar = document.querySelector( - 'section[class*="PullRequestFilesToolbar-module__toolbar"]' + 'section[class*="toolbar__"]' ); if (prToolbar) { const rightControls = prToolbar.children[2]; diff --git a/src/features/pr-dashboard.test.ts b/src/features/pr-dashboard.test.ts index b4e3310..64c3dd6 100644 --- a/src/features/pr-dashboard.test.ts +++ b/src/features/pr-dashboard.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { dashboardRow, repoDashboard } from "../test-utils/pr-dashboard"; +import { dashboardRow as buildDashboardRow, repoDashboard } from "../test-utils/pr-dashboard"; import { setUrl } from "../test-utils/url"; import { collectPRRows } from "../lib/pr-list-dom"; import { reserveInfoRowSkeletons } from "../lib/info-row-skeleton"; @@ -23,7 +23,8 @@ async function injectBadges(): Promise { await Promise.all([injectPRBranchNames(), injectPRDiffStats(), injectPRReviewStatus()]); } -describe("repository PR dashboard preview", () => { +describe.each(["prefixed", "current"] as const)("repository PR dashboard (%s DOM)", (dom) => { + const dashboardRow = (number: number, label?: string) => buildDashboardRow(number, label, dom); beforeEach(() => { vi.clearAllMocks(); setUrl("https://github.com/owner/repo/pulls"); @@ -51,7 +52,8 @@ describe("repository PR dashboard preview", () => { expect(document.querySelectorAll(".bg-skeleton-pill")).toHaveLength(2); await injectBadges(); const info = document.querySelector(".better-github-info-row")!; - expect(info.previousElementSibling?.className).toContain("Description-module__container"); + expect(info.previousElementSibling?.textContent).toBe("#7 ยท author opened yesterday"); + expect(document.querySelector(".inner__BgjhSm .better-github-info-row")).toBeNull(); expect([...info.children].map((el) => (el as HTMLElement).dataset.bgInfoRowItem)).toEqual([ "branch", "diff", diff --git a/src/lib/commit-dom.ts b/src/lib/commit-dom.ts index d84c709..3e3ffb5 100644 --- a/src/lib/commit-dom.ts +++ b/src/lib/commit-dom.ts @@ -3,10 +3,6 @@ // Covers both classic GitHub DOM (
  • ) and new React DOM (div.TimelineItem-body / div[data-testid]). export const COMMIT_ROW_SELECTOR = "li, div.TimelineItem-body, div[data-testid]"; -// GitHub's commit content wrapper now omits the CSS-module name prefix. -export const MAIN_CONTENT_INNER_SELECTOR = - "[class*='MainContent-module__inner'], [class^='inner__']"; - export function collectCommitRows(owner: string, repo: string): Map { const pattern = new RegExp(`^/${owner}/${repo}/commit/([0-9a-f]{40})$`, "i"); const result = new Map(); diff --git a/src/lib/info-row-skeleton.test.ts b/src/lib/info-row-skeleton.test.ts index bf90f03..a65b77e 100644 --- a/src/lib/info-row-skeleton.test.ts +++ b/src/lib/info-row-skeleton.test.ts @@ -106,7 +106,7 @@ describe("reserveInfoRowSkeletons", () => { document.body.innerHTML = `
  • -
    author and checks
    +
    author and checks
  • `; diff --git a/src/lib/info-row-skeleton.ts b/src/lib/info-row-skeleton.ts index af943ff..6aff9d6 100644 --- a/src/lib/info-row-skeleton.ts +++ b/src/lib/info-row-skeleton.ts @@ -1,6 +1,7 @@ import { collectPRRows, PR_DASHBOARD_SELECTOR } from "./pr-list-dom"; import { isPRListPage, isCommitsListPage, getRepoInfo } from "./page-detect"; -import { collectCommitRows, MAIN_CONTENT_INNER_SELECTOR } from "./commit-dom"; +import { collectCommitRows } from "./commit-dom"; +import { MAIN_CONTENT_INNER_SELECTOR } from "./list-view-dom"; import { INFO_ROW_CLASS, insertInfoRowItem } from "./info-row"; export type SkeletonKind = "branch" | "prDiff" | "commitDiff"; diff --git a/src/lib/info-row.test.ts b/src/lib/info-row.test.ts index 7e70f18..8bee646 100644 --- a/src/lib/info-row.test.ts +++ b/src/lib/info-row.test.ts @@ -35,6 +35,21 @@ describe("getOrCreateInfoRow", () => { expect(meta.nextElementSibling).toBe(infoRow); }); + it("uses the current content wrapper without mounting inside the status icon", () => { + document.body.innerHTML = ` +
  • +
    Open
    +
    Metadata
    +
  • + `; + + const infoRow = getOrCreateInfoRow(document.querySelector("li")!); + + expect(infoRow).not.toBeNull(); + expect(infoRow?.parentElement).toBe(document.querySelector(".inner__Rm1Enx")); + expect(document.querySelector(".inner__BgjhSm .better-github-info-row")).toBeNull(); + }); + it("falls back to inserting after the title link", () => { document.body.innerHTML = `
  • diff --git a/src/lib/info-row.ts b/src/lib/info-row.ts index 93cb13a..4de8fbd 100644 --- a/src/lib/info-row.ts +++ b/src/lib/info-row.ts @@ -1,5 +1,6 @@ import { isCompactPRRow } from "./pr-list-dom"; import { isPRListReady } from "./pr-list-ready"; +import { MAIN_CONTENT_INNER_SELECTOR } from "./list-view-dom"; /** * Shared "info row" below the meta line in PR/issue list items. @@ -26,7 +27,7 @@ export function getOrCreateInfoRow(row: Element): HTMLElement | null { infoRow.className = INFO_ROW_CLASS; // New GitHub DOM: append inside MainContent inner container - const mainInner = row.querySelector("[class*='MainContent-module__inner']"); + const mainInner = row.querySelector(MAIN_CONTENT_INNER_SELECTOR); if (mainInner) { mainInner.appendChild(infoRow); return infoRow; diff --git a/src/lib/list-view-dom.ts b/src/lib/list-view-dom.ts new file mode 100644 index 0000000..baca192 --- /dev/null +++ b/src/lib/list-view-dom.ts @@ -0,0 +1,4 @@ +// GitHub now omits CSS-module name prefixes. The parent distinguishes main +// content from the status icon's separate inner__ wrapper. +export const MAIN_CONTENT_INNER_SELECTOR = + "[class*='MainContent-module__inner'], div[class^='container__'] > div[class^='inner__']"; diff --git a/src/styles/skeleton-reserve.css b/src/styles/skeleton-reserve.css index c5de8f7..adc7b36 100644 --- a/src/styles/skeleton-reserve.css +++ b/src/styles/skeleton-reserve.css @@ -13,14 +13,25 @@ */ [data-bg-page="pr-list"] .js-issue-row::after, -[data-bg-page="pr-list"] li[role="listitem"] [class*="MainContent-module__inner"]::after, +[data-bg-page="pr-list"] + li[role="listitem"] + :is( + [class*="MainContent-module__inner"], + div[class^="container__"] > div[class^="inner__"] + )::after, [data-bg-page="pr-list"] react-app:is([app-name="pull-requests"], [app-name="repo"]) li - [class*="MainContent-module__inner"]::after, + :is( + [class*="MainContent-module__inner"], + div[class^="container__"] > div[class^="inner__"] + )::after, [data-bg-page="commits-list"] li[data-testid="commit-row-item"] - :is([class*="MainContent-module__inner"], [class^="inner__"])::after { + :is( + [class*="MainContent-module__inner"], + div[class^="container__"] > div[class^="inner__"] + )::after { content: ""; display: block; height: 20px; @@ -31,17 +42,24 @@ [data-bg-page="pr-list"] react-app:is([app-name="pull-requests"], [app-name="repo"]) [data-density="compact"] - [class*="MainContent-module__inner"]::after, + :is( + [class*="MainContent-module__inner"], + div[class^="container__"] > div[class^="inner__"] + )::after, [data-bg-page="pr-list"] li[role="listitem"] - [class*="MainContent-module__inner"]:has(.better-github-info-row)::after, + :is([class*="MainContent-module__inner"], div[class^="container__"] > div[class^="inner__"]):has( + .better-github-info-row + )::after, [data-bg-page="pr-list"] react-app:is([app-name="pull-requests"], [app-name="repo"]) li - [class*="MainContent-module__inner"]:has(.better-github-info-row)::after, + :is([class*="MainContent-module__inner"], div[class^="container__"] > div[class^="inner__"]):has( + .better-github-info-row + )::after, [data-bg-page="commits-list"] li[data-testid="commit-row-item"] - :is([class*="MainContent-module__inner"], [class^="inner__"]):has( + :is([class*="MainContent-module__inner"], div[class^="container__"] > div[class^="inner__"]):has( .better-github-commit-diff-stats, .bg-skeleton-pill--commit-diff )::after { diff --git a/src/test-utils/pr-dashboard.ts b/src/test-utils/pr-dashboard.ts index e631b15..b7eb297 100644 --- a/src/test-utils/pr-dashboard.ts +++ b/src/test-utils/pr-dashboard.ts @@ -1,7 +1,11 @@ // Reduced from the repository pull-requests preview DOM. Rows have neither // issue_ IDs nor explicit listitem roles; labels are React buttons. -export function dashboardRow(number: number, label = "Feature"): string { - return `
  • +export function dashboardRow( + number: number, + label = "Feature", + dom: "prefixed" | "current" = "prefixed", +): string { + const row = `
  • PR ${number}

    @@ -14,6 +18,19 @@ export function dashboardRow(number: number, label = "Feature"): string {
  • `; + if (dom === "prefixed") return row; + return row + .replace("Title-module__trailingBadgesContainer__INeSa", "trailingBadgesContainer__4AEjpg") + .replace( + "MainContent-module__container__uXXH2", + "container__VGL_Vt inlineChecksMainContent__kclwVu", + ) + .replace("MainContent-module__inner__C80co", "inner__Rm1Enx") + .replace("Description-module__container__hpqJz", "container__W45oI6 description__mZjLFE") + .replace( + '
    ', + '
    Open
    ', + ); } export function dashboard(...rows: string[]): string {