import type { EnvironmentId, ProjectId, PullRequestListEntry } from "@t3tools/contracts";import { describe, expect, it } from "vite-plus/test"; import { filterPullRequestsByInvolvement, findScopedProject, mergePullRequestLists, pullRequestEntryKey, pullRequestEnvironmentSetKey, groupPullRequestsByInvolvement, matchesPullRequestFilters, matchesPullRequestQuery, parsePullRequestQuery, pullRequestStatsBatches, pullRequestStatsKeysToRequest, pullRequestStatsRefreshBatches, pullRequestStatsRequestBatches, mergePullRequestDiffStats, narrowPullRequestsToFilters, partitionPullRequestsWithPriority, readPullRequestListSnapshot, writePullRequestListSnapshot, rankPullRequestMatches, rankPullRequestsByMergeReadiness, scorePullRequestMatch, sortPullRequestGroups, retainVisiblePullRequestStatsBatches, withDiffStat, resolveProjectScope, resolveQueryEnvironmentIds, resolveSelectedEnvironmentId, type EnvironmentPullRequestEntry,} from "./pullRequestList.logic";import { pullRequestListPreferences, readPullRequestListPreferences, writePullRequestListPreferences,} from "./pullRequestListPreferences"; const VIEWERS = { "github.com": "Bilal" } as const;const NO_VIEWERS = {} as const; function entry( overrides: Partial<EnvironmentPullRequestEntry> & Pick<PullRequestListEntry, "number">,) { return { environmentId: "env-1", provider: "github", host: "github.com", projectId: "project-1", projectTitle: "t3code", repository: "pingdotgg/t3code", title: "Add the pull requests page", url: `https://github.com/pingdotgg/t3code/pull/${overrides.number}`, author: { login: "octocat", name: null, avatarUrl: null }, headBranch: `feat/branch-${overrides.number}`, baseBranch: "main", state: "open", isDraft: false, mergeability: "mergeable", additions: 1, deletions: 0, createdAt: "2026-07-01T00:00:00Z", updatedAt: "2026-07-02T00:00:00Z", viewerReviewRequested: false, labels: [], ...overrides, } as EnvironmentPullRequestEntry;} describe("visible pull request line-count targets", () => { it("reuses counts supplied by the listing and only requests missing counts", () => { const entries = [ entry({ number: 1, additions: 12, deletions: 0 }), entry({ number: 2, additions: 0, deletions: 7 }), entry({ number: 3, additions: 0, deletions: 0 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const keys = pullRequestStatsKeysToRequest( entriesByKey, new Set(entries.map(pullRequestEntryKey)), [], new Map(), ); expect(pullRequestStatsBatches(entriesByKey, keys)[0]?.input.refs).toEqual([ { projectId: "project-1", repository: "pingdotgg/t3code", number: 3 }, ]); }); it("does not request a row again after its received batch is pruned", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const firstKey = pullRequestEntryKey(entries[0]!); const secondKey = pullRequestEntryKey(entries[1]!); const completedStats = mergePullRequestDiffStats(new Map(), [ { environmentId: ENV_1, projectId: "project-1", number: 1, additions: 1, deletions: 1, }, ]); const keys = pullRequestStatsKeysToRequest( entriesByKey, new Set([firstKey, secondKey]), [], completedStats, ); expect([...keys]).toEqual([secondKey]); expect(pullRequestStatsBatches(entriesByKey, keys)[0]?.input.refs).toEqual([ { projectId: "project-1", repository: "pingdotgg/t3code", number: 2 }, ]); }); it("drops historical rows after a long scroll so refresh stays bounded to the viewport", () => { const entries = Array.from({ length: 500 }, (_, index) => entry({ additions: 0, deletions: 0, number: index + 1 }), ); const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const batches = entries.map( (item) => pullRequestStatsBatches(entriesByKey, new Set([pullRequestEntryKey(item)]))[0]!, ); const visibleKeys = new Set(entries.slice(-12).map(pullRequestEntryKey)); const retained = retainVisiblePullRequestStatsBatches(batches, visibleKeys); expect(retained).toHaveLength(12); expect(retained.flatMap((batch) => batch.input.refs.map((ref) => ref.number))).toEqual( entries.slice(-12).map((item) => item.number), ); }); it("keeps every per-environment batch within the stats contract limit", () => { const entries = Array.from({ length: 501 }, (_, index) => entry({ additions: 0, deletions: 0, number: index + 1 }), ); const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const batches = pullRequestStatsBatches( entriesByKey, new Set(entries.map(pullRequestEntryKey)), ); expect(batches.map((batch) => batch.input.refs.length)).toEqual([500, 1]); expect(batches.flatMap((batch) => [...batch.keys])).toEqual(entries.map(pullRequestEntryKey)); }); it("selects visible rows for date modes and every uncached row for size modes", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), entry({ additions: 0, deletions: 0, number: 3, environmentId: "env-2" as EnvironmentId }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const [firstKey, secondKey] = entries.map(pullRequestEntryKey); const cachedStats = mergePullRequestDiffStats(new Map(), [ { environmentId: ENV_1, projectId: "project-1", number: 1, additions: 1, deletions: 1, }, ]); const visible = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set([firstKey!, secondKey!]), policy: "visible", activeBatches: [], statsByRow: cachedStats, }); expect(visible.flatMap((batch) => batch.input.refs.map((ref) => ref.number))).toEqual([2]); const eager = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set([firstKey!]), policy: "eager", activeBatches: [], statsByRow: cachedStats, }); expect(eager.map((batch) => batch.environmentId)).toEqual([ENV_1, "env-2"]); expect(eager.flatMap((batch) => batch.input.refs.map((ref) => ref.number))).toEqual([2, 3]); }); it("refreshes only visible rows unless size sorting needs every loaded row", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), entry({ additions: 0, deletions: 0, number: 3 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const visibleKeys = new Set([pullRequestEntryKey(entries[1]!)]); const cachedStats = mergePullRequestDiffStats( new Map(), entries.map((item) => ({ environmentId: ENV_1, projectId: item.projectId, number: item.number, additions: item.additions, deletions: item.deletions, })), ); const visible = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: visibleKeys, policy: "visible", activeBatches: [], statsByRow: cachedStats, refresh: true, }); expect(visible[0]?.input.refs.map((ref) => ref.number)).toEqual([2]); const eager = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: visibleKeys, policy: "eager", activeBatches: [], statsByRow: cachedStats, refresh: true, }); expect(eager[0]?.input.refs.map((ref) => ref.number)).toEqual([1, 2, 3]); }); it("ignores a late refresh after the filter or stats policy changes", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const visibleKeys = new Set([pullRequestEntryKey(entries[1]!)]); const requestedScope = { key: "open", policy: "visible" } as const; const refresh = (currentScope: { key: string; policy: "visible" | "eager" }) => pullRequestStatsRefreshBatches({ requestedScope, currentScope, entriesByKey, candidateKeys: visibleKeys, statsByRow: new Map(), }); expect(refresh(requestedScope)?.[0]?.input.refs.map((ref) => ref.number)).toEqual([2]); expect(refresh({ key: "closed", policy: "visible" })).toBeNull(); expect(refresh({ key: "open", policy: "eager" })).toBeNull(); }); it("does not add request batches again while rows are active or cached", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const first = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set(), policy: "eager", activeBatches: [], statsByRow: new Map(), }); expect( pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set(), policy: "eager", activeBatches: first, statsByRow: new Map(), }), ).toEqual([]); const cachedStats = mergePullRequestDiffStats( new Map(), entries.map((item) => ({ environmentId: ENV_1, projectId: item.projectId, number: item.number, additions: item.additions, deletions: item.deletions, })), ); expect( pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set(entries.map(pullRequestEntryKey)), policy: "visible", activeBatches: [], statsByRow: cachedStats, }), ).toEqual([]); });}); describe("pull request involvement filtering", () => { const entries = [ entry({ number: 1, author: { login: "Bilal", name: null, avatarUrl: null } }), entry({ number: 2, viewerReviewRequested: true }), entry({ number: 3 }), ]; it("matches the viewer's own pull requests case-insensitively", () => { expect( filterPullRequestsByInvolvement(entries, VIEWERS, "authored").map((item) => item.number), ).toEqual([1]); }); it("returns nothing for Authored when the viewer is unknown", () => { expect(filterPullRequestsByInvolvement(entries, NO_VIEWERS, "authored")).toEqual([]); }); it("uses the server-computed review-request flag for Reviewing", () => { expect( filterPullRequestsByInvolvement(entries, VIEWERS, "reviewing").map((item) => item.number), ).toEqual([2]); }); it("does not treat a matching login on another host as the viewer", () => { // The same name on GitLab is a different account, and its viewer is unknown here. const mixed = [ entry({ number: 1, author: { login: "Bilal", name: null, avatarUrl: null } }), entry({ number: 2, provider: "gitlab", host: "gitlab.com", author: { login: "Bilal", name: null, avatarUrl: null }, }), ]; expect( filterPullRequestsByInvolvement(mixed, VIEWERS, "authored").map((item) => item.number), ).toEqual([1]); }); it("leaves the superset untouched for All", () => { expect(filterPullRequestsByInvolvement(entries, VIEWERS, "all")).toHaveLength(3); }); it("keeps two hosts of one provider kind as two accounts", () => { // A GitHub Enterprise install is a different account from github.com, so the viewer for // one must not claim authorship of the other's change requests. const mixed = [ entry({ number: 1, author: { login: "Bilal", name: null, avatarUrl: null } }), entry({ number: 2, host: "github.acme.dev", author: { login: "Bilal", name: null, avatarUrl: null }, }), ]; expect( filterPullRequestsByInvolvement(mixed, VIEWERS, "authored").map((item) => item.number), ).toEqual([1]); });}); describe("pull request grouping", () => { it("buckets authored above review-requested and drops empty groups", () => { const groups = groupPullRequestsByInvolvement( [ entry({ number: 1, author: { login: "bilal", name: null, avatarUrl: null } }), entry({ number: 2, viewerReviewRequested: true }), ], VIEWERS, ); expect(groups.map((group) => [group.key, group.entries.length])).toEqual([ ["authored", 1], ["reviewRequested", 1], ]); }); it("puts everything in Others when the viewer is unknown", () => { const groups = groupPullRequestsByInvolvement([entry({ number: 1 })], NO_VIEWERS); expect(groups.map((group) => group.key)).toEqual(["others"]); }); it("counts an authored pull request once, even with a review request on it", () => { const groups = groupPullRequestsByInvolvement( [ entry({ number: 1, author: { login: "bilal", name: null, avatarUrl: null }, viewerReviewRequested: true, }), ], VIEWERS, ); expect(groups.map((group) => group.key)).toEqual(["authored"]); });}); describe("pull request search", () => { const target = entry({ number: 4711, title: "Restore sidebar actions", headBranch: "fix/sidebar", }); it("matches the number with or without the leading hash", () => { expect(matchesPullRequestQuery(target, "#4711")).toBe(true); expect(matchesPullRequestQuery(target, "4711")).toBe(true); }); it("matches title, branch and author case-insensitively", () => { expect(matchesPullRequestQuery(target, "SIDEBAR")).toBe(true); expect(matchesPullRequestQuery(target, "fix/side")).toBe(true); expect(matchesPullRequestQuery(target, "octocat")).toBe(true); }); it("ignores surrounding whitespace and rejects non-matches", () => { expect(matchesPullRequestQuery(target, " ")).toBe(true); expect(matchesPullRequestQuery(target, "kanban")).toBe(false); });}); describe("carrying rows already read into filters nothing has answered yet", () => { const rows = [ entry({ number: 1 }), entry({ number: 2, state: "merged" }), entry({ number: 3, state: "closed" }), entry({ number: 4, host: "github.acme.dev" }), entry({ number: 5, projectId: "project-2" as PullRequestListEntry["projectId"] }), ]; const everything = { state: "all", projectId: undefined, host: undefined } as const; it("never lets a merged pull request sit under Open", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, state: "open" }).map((row) => row.number), ).toEqual([1, 4, 5]); }); it("keeps only the state that was asked for", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, state: "merged" }).map( (row) => row.number, ), ).toEqual([2]); }); it("narrows to one host, which two installs of one kind cannot share", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, host: "github.acme.dev" }).map( (row) => row.number, ), ).toEqual([4]); }); it("narrows to one project", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, projectId: "project-2" }).map( (row) => row.number, ), ).toEqual([5]); }); it("holds on to everything when nothing narrows it", () => { expect(narrowPullRequestsToFilters(rows, everything)).toEqual(rows); });}); describe("narrowing rows by the filters a host may not have applied", () => { const rows = [ entry({ number: 1 }), entry({ number: 2, isDraft: true }), entry({ number: 3, reviewDecision: "approved" }), entry({ number: 4, labels: [{ name: "Needs design", color: null }], author: { login: "Hubot", name: null, avatarUrl: null }, reviewDecision: "changes-requested", }), ]; const narrow = (filters: Parameters<typeof matchesPullRequestFilters>[1]) => rows.filter((row) => matchesPullRequestFilters(row, filters)).map((row) => row.number); it("keeps or drops drafts as asked", () => { expect(narrow({ draft: "only" })).toEqual([2]); expect(narrow({ draft: "hide" })).toEqual([1, 3, 4]); }); it("keeps the review state that was asked for, and no reviews means none at all", () => { expect(narrow({ review: "approved" })).toEqual([3]); expect(narrow({ review: "changes-requested" })).toEqual([4]); expect(narrow({ review: "none" })).toEqual([1, 2]); }); it("matches labels and authors however either was capitalized", () => { expect(narrow({ labels: [["needs DESIGN"]] })).toEqual([4]); expect(narrow({ excludedLabels: ["needs design"] })).toEqual([1, 2, 3]); expect(narrow({ author: "hubot" })).toEqual([4]); }); it("resolves author:me to the given viewer, case-insensitively", () => { const mine = entry({ number: 30, author: { login: "Bilal", name: null, avatarUrl: null } }); const theirs = entry({ number: 31, author: { login: "Hubot", name: null, avatarUrl: null } }); expect(matchesPullRequestFilters(mine, { author: "me" }, "bilal")).toBe(true); expect(matchesPullRequestFilters(theirs, { author: "me" }, "bilal")).toBe(false); }); Lines 1–500 of 1498. Load more
import type { EnvironmentId, ProjectId, PullRequestListEntry } from "@t3tools/contracts"; import { describe, expect, it } from "vite-plus/test"; import { filterPullRequestsByInvolvement, findScopedProject, mergePullRequestLists, pullRequestEntryKey, pullRequestEnvironmentSetKey, groupPullRequestsByInvolvement, matchesPullRequestFilters, matchesPullRequestQuery, parsePullRequestQuery, pullRequestStatsBatches, pullRequestStatsKeysToRequest, pullRequestStatsRefreshBatches, pullRequestStatsRequestBatches, mergePullRequestDiffStats, narrowPullRequestsToFilters, partitionPullRequestsWithPriority, readPullRequestListSnapshot, writePullRequestListSnapshot, rankPullRequestMatches, rankPullRequestsByMergeReadiness, scorePullRequestMatch, sortPullRequestGroups, retainVisiblePullRequestStatsBatches, withDiffStat, resolveProjectScope, resolveQueryEnvironmentIds, resolveSelectedEnvironmentId, type EnvironmentPullRequestEntry, } from "./pullRequestList.logic"; import { pullRequestListPreferences, readPullRequestListPreferences, writePullRequestListPreferences, } from "./pullRequestListPreferences"; const VIEWERS = { "github.com": "Bilal" } as const; const NO_VIEWERS = {} as const; function entry( overrides: Partial<EnvironmentPullRequestEntry> & Pick<PullRequestListEntry, "number">, ) { return { environmentId: "env-1", provider: "github", host: "github.com", projectId: "project-1", projectTitle: "t3code", repository: "pingdotgg/t3code", title: "Add the pull requests page", url: `https://github.com/pingdotgg/t3code/pull/${overrides.number}`, author: { login: "octocat", name: null, avatarUrl: null }, headBranch: `feat/branch-${overrides.number}`, baseBranch: "main", state: "open", isDraft: false, mergeability: "mergeable", additions: 1, deletions: 0, createdAt: "2026-07-01T00:00:00Z", updatedAt: "2026-07-02T00:00:00Z", viewerReviewRequested: false, labels: [], ...overrides, } as EnvironmentPullRequestEntry; } describe("visible pull request line-count targets", () => { it("reuses counts supplied by the listing and only requests missing counts", () => { const entries = [ entry({ number: 1, additions: 12, deletions: 0 }), entry({ number: 2, additions: 0, deletions: 7 }), entry({ number: 3, additions: 0, deletions: 0 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const keys = pullRequestStatsKeysToRequest( entriesByKey, new Set(entries.map(pullRequestEntryKey)), [], new Map(), ); expect(pullRequestStatsBatches(entriesByKey, keys)[0]?.input.refs).toEqual([ { projectId: "project-1", repository: "pingdotgg/t3code", number: 3 }, ]); }); it("does not request a row again after its received batch is pruned", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const firstKey = pullRequestEntryKey(entries[0]!); const secondKey = pullRequestEntryKey(entries[1]!); const completedStats = mergePullRequestDiffStats(new Map(), [ { environmentId: ENV_1, projectId: "project-1", number: 1, additions: 1, deletions: 1, }, ]); const keys = pullRequestStatsKeysToRequest( entriesByKey, new Set([firstKey, secondKey]), [], completedStats, ); expect([...keys]).toEqual([secondKey]); expect(pullRequestStatsBatches(entriesByKey, keys)[0]?.input.refs).toEqual([ { projectId: "project-1", repository: "pingdotgg/t3code", number: 2 }, ]); }); it("drops historical rows after a long scroll so refresh stays bounded to the viewport", () => { const entries = Array.from({ length: 500 }, (_, index) => entry({ additions: 0, deletions: 0, number: index + 1 }), ); const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const batches = entries.map( (item) => pullRequestStatsBatches(entriesByKey, new Set([pullRequestEntryKey(item)]))[0]!, ); const visibleKeys = new Set(entries.slice(-12).map(pullRequestEntryKey)); const retained = retainVisiblePullRequestStatsBatches(batches, visibleKeys); expect(retained).toHaveLength(12); expect(retained.flatMap((batch) => batch.input.refs.map((ref) => ref.number))).toEqual( entries.slice(-12).map((item) => item.number), ); }); it("keeps every per-environment batch within the stats contract limit", () => { const entries = Array.from({ length: 501 }, (_, index) => entry({ additions: 0, deletions: 0, number: index + 1 }), ); const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const batches = pullRequestStatsBatches( entriesByKey, new Set(entries.map(pullRequestEntryKey)), ); expect(batches.map((batch) => batch.input.refs.length)).toEqual([500, 1]); expect(batches.flatMap((batch) => [...batch.keys])).toEqual(entries.map(pullRequestEntryKey)); }); it("selects visible rows for date modes and every uncached row for size modes", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), entry({ additions: 0, deletions: 0, number: 3, environmentId: "env-2" as EnvironmentId }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const [firstKey, secondKey] = entries.map(pullRequestEntryKey); const cachedStats = mergePullRequestDiffStats(new Map(), [ { environmentId: ENV_1, projectId: "project-1", number: 1, additions: 1, deletions: 1, }, ]); const visible = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set([firstKey!, secondKey!]), policy: "visible", activeBatches: [], statsByRow: cachedStats, }); expect(visible.flatMap((batch) => batch.input.refs.map((ref) => ref.number))).toEqual([2]); const eager = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set([firstKey!]), policy: "eager", activeBatches: [], statsByRow: cachedStats, }); expect(eager.map((batch) => batch.environmentId)).toEqual([ENV_1, "env-2"]); expect(eager.flatMap((batch) => batch.input.refs.map((ref) => ref.number))).toEqual([2, 3]); }); it("refreshes only visible rows unless size sorting needs every loaded row", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), entry({ additions: 0, deletions: 0, number: 3 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const visibleKeys = new Set([pullRequestEntryKey(entries[1]!)]); const cachedStats = mergePullRequestDiffStats( new Map(), entries.map((item) => ({ environmentId: ENV_1, projectId: item.projectId, number: item.number, additions: item.additions, deletions: item.deletions, })), ); const visible = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: visibleKeys, policy: "visible", activeBatches: [], statsByRow: cachedStats, refresh: true, }); expect(visible[0]?.input.refs.map((ref) => ref.number)).toEqual([2]); const eager = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: visibleKeys, policy: "eager", activeBatches: [], statsByRow: cachedStats, refresh: true, }); expect(eager[0]?.input.refs.map((ref) => ref.number)).toEqual([1, 2, 3]); }); it("ignores a late refresh after the filter or stats policy changes", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const visibleKeys = new Set([pullRequestEntryKey(entries[1]!)]); const requestedScope = { key: "open", policy: "visible" } as const; const refresh = (currentScope: { key: string; policy: "visible" | "eager" }) => pullRequestStatsRefreshBatches({ requestedScope, currentScope, entriesByKey, candidateKeys: visibleKeys, statsByRow: new Map(), }); expect(refresh(requestedScope)?.[0]?.input.refs.map((ref) => ref.number)).toEqual([2]); expect(refresh({ key: "closed", policy: "visible" })).toBeNull(); expect(refresh({ key: "open", policy: "eager" })).toBeNull(); }); it("does not add request batches again while rows are active or cached", () => { const entries = [ entry({ additions: 0, deletions: 0, number: 1 }), entry({ additions: 0, deletions: 0, number: 2 }), ]; const entriesByKey = new Map(entries.map((item) => [pullRequestEntryKey(item), item])); const first = pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set(), policy: "eager", activeBatches: [], statsByRow: new Map(), }); expect( pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set(), policy: "eager", activeBatches: first, statsByRow: new Map(), }), ).toEqual([]); const cachedStats = mergePullRequestDiffStats( new Map(), entries.map((item) => ({ environmentId: ENV_1, projectId: item.projectId, number: item.number, additions: item.additions, deletions: item.deletions, })), ); expect( pullRequestStatsRequestBatches({ entriesByKey, candidateKeys: new Set(entries.map(pullRequestEntryKey)), policy: "visible", activeBatches: [], statsByRow: cachedStats, }), ).toEqual([]); }); }); describe("pull request involvement filtering", () => { const entries = [ entry({ number: 1, author: { login: "Bilal", name: null, avatarUrl: null } }), entry({ number: 2, viewerReviewRequested: true }), entry({ number: 3 }), ]; it("matches the viewer's own pull requests case-insensitively", () => { expect( filterPullRequestsByInvolvement(entries, VIEWERS, "authored").map((item) => item.number), ).toEqual([1]); }); it("returns nothing for Authored when the viewer is unknown", () => { expect(filterPullRequestsByInvolvement(entries, NO_VIEWERS, "authored")).toEqual([]); }); it("uses the server-computed review-request flag for Reviewing", () => { expect( filterPullRequestsByInvolvement(entries, VIEWERS, "reviewing").map((item) => item.number), ).toEqual([2]); }); it("does not treat a matching login on another host as the viewer", () => { // The same name on GitLab is a different account, and its viewer is unknown here. const mixed = [ entry({ number: 1, author: { login: "Bilal", name: null, avatarUrl: null } }), entry({ number: 2, provider: "gitlab", host: "gitlab.com", author: { login: "Bilal", name: null, avatarUrl: null }, }), ]; expect( filterPullRequestsByInvolvement(mixed, VIEWERS, "authored").map((item) => item.number), ).toEqual([1]); }); it("leaves the superset untouched for All", () => { expect(filterPullRequestsByInvolvement(entries, VIEWERS, "all")).toHaveLength(3); }); it("keeps two hosts of one provider kind as two accounts", () => { // A GitHub Enterprise install is a different account from github.com, so the viewer for // one must not claim authorship of the other's change requests. const mixed = [ entry({ number: 1, author: { login: "Bilal", name: null, avatarUrl: null } }), entry({ number: 2, host: "github.acme.dev", author: { login: "Bilal", name: null, avatarUrl: null }, }), ]; expect( filterPullRequestsByInvolvement(mixed, VIEWERS, "authored").map((item) => item.number), ).toEqual([1]); }); }); describe("pull request grouping", () => { it("buckets authored above review-requested and drops empty groups", () => { const groups = groupPullRequestsByInvolvement( [ entry({ number: 1, author: { login: "bilal", name: null, avatarUrl: null } }), entry({ number: 2, viewerReviewRequested: true }), ], VIEWERS, ); expect(groups.map((group) => [group.key, group.entries.length])).toEqual([ ["authored", 1], ["reviewRequested", 1], ]); }); it("puts everything in Others when the viewer is unknown", () => { const groups = groupPullRequestsByInvolvement([entry({ number: 1 })], NO_VIEWERS); expect(groups.map((group) => group.key)).toEqual(["others"]); }); it("counts an authored pull request once, even with a review request on it", () => { const groups = groupPullRequestsByInvolvement( [ entry({ number: 1, author: { login: "bilal", name: null, avatarUrl: null }, viewerReviewRequested: true, }), ], VIEWERS, ); expect(groups.map((group) => group.key)).toEqual(["authored"]); }); }); describe("pull request search", () => { const target = entry({ number: 4711, title: "Restore sidebar actions", headBranch: "fix/sidebar", }); it("matches the number with or without the leading hash", () => { expect(matchesPullRequestQuery(target, "#4711")).toBe(true); expect(matchesPullRequestQuery(target, "4711")).toBe(true); }); it("matches title, branch and author case-insensitively", () => { expect(matchesPullRequestQuery(target, "SIDEBAR")).toBe(true); expect(matchesPullRequestQuery(target, "fix/side")).toBe(true); expect(matchesPullRequestQuery(target, "octocat")).toBe(true); }); it("ignores surrounding whitespace and rejects non-matches", () => { expect(matchesPullRequestQuery(target, " ")).toBe(true); expect(matchesPullRequestQuery(target, "kanban")).toBe(false); }); }); describe("carrying rows already read into filters nothing has answered yet", () => { const rows = [ entry({ number: 1 }), entry({ number: 2, state: "merged" }), entry({ number: 3, state: "closed" }), entry({ number: 4, host: "github.acme.dev" }), entry({ number: 5, projectId: "project-2" as PullRequestListEntry["projectId"] }), ]; const everything = { state: "all", projectId: undefined, host: undefined } as const; it("never lets a merged pull request sit under Open", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, state: "open" }).map((row) => row.number), ).toEqual([1, 4, 5]); }); it("keeps only the state that was asked for", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, state: "merged" }).map( (row) => row.number, ), ).toEqual([2]); }); it("narrows to one host, which two installs of one kind cannot share", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, host: "github.acme.dev" }).map( (row) => row.number, ), ).toEqual([4]); }); it("narrows to one project", () => { expect( narrowPullRequestsToFilters(rows, { ...everything, projectId: "project-2" }).map( (row) => row.number, ), ).toEqual([5]); }); it("holds on to everything when nothing narrows it", () => { expect(narrowPullRequestsToFilters(rows, everything)).toEqual(rows); }); }); describe("narrowing rows by the filters a host may not have applied", () => { const rows = [ entry({ number: 1 }), entry({ number: 2, isDraft: true }), entry({ number: 3, reviewDecision: "approved" }), entry({ number: 4, labels: [{ name: "Needs design", color: null }], author: { login: "Hubot", name: null, avatarUrl: null }, reviewDecision: "changes-requested", }), ]; const narrow = (filters: Parameters<typeof matchesPullRequestFilters>[1]) => rows.filter((row) => matchesPullRequestFilters(row, filters)).map((row) => row.number); it("keeps or drops drafts as asked", () => { expect(narrow({ draft: "only" })).toEqual([2]); expect(narrow({ draft: "hide" })).toEqual([1, 3, 4]); }); it("keeps the review state that was asked for, and no reviews means none at all", () => { expect(narrow({ review: "approved" })).toEqual([3]); expect(narrow({ review: "changes-requested" })).toEqual([4]); expect(narrow({ review: "none" })).toEqual([1, 2]); }); it("matches labels and authors however either was capitalized", () => { expect(narrow({ labels: [["needs DESIGN"]] })).toEqual([4]); expect(narrow({ excludedLabels: ["needs design"] })).toEqual([1, 2, 3]); expect(narrow({ author: "hubot" })).toEqual([4]); }); it("resolves author:me to the given viewer, case-insensitively", () => { const mine = entry({ number: 30, author: { login: "Bilal", name: null, avatarUrl: null } }); const theirs = entry({ number: 31, author: { login: "Hubot", name: null, avatarUrl: null } }); expect(matchesPullRequestFilters(mine, { author: "me" }, "bilal")).toBe(true); expect(matchesPullRequestFilters(theirs, { author: "me" }, "bilal")).toBe(false); });