Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions apps/mobile/src/features/pull-requests/pullRequestList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import {
mergeEnvironmentPullRequestPages,
nextPullRequestListInput,
PR_LIST_PAGE_SIZE,
patchPullRequestListEntry,
pullRequestFilterChoices,
pullRequestLabelColors,
pullRequestListHead,
pullRequestListInput,
scopedPullRequestListKey,
shownPullRequestListPages,
pullRequestListRowsAreEqual,
withShownPullRequestStats,
withoutScratchProjectScope,
} from "./pullRequestList";

Expand Down Expand Up @@ -115,6 +118,14 @@ describe("mobile PR list pagination", () => {
mergePullRequestListEntries([entry(1)], [entry(2, { host: "enterprise.test" }), entry(2)]),
).toHaveLength(3);
});
it("patches a row that only the list head supplies, and keeps it while that head is current", () => {
const head = page([entry(2), entry(1)], { repo: "first" });
const patched = patchPullRequestListEntry(null, head, entry(1), { state: "merged" }, 5);
expect(shownPullRequestListPages(patched, head)?.entries.map((row) => row.state)).toEqual([
"open",
"merged",
]);
});
it("refreshes the head without dropping older loaded pages or rewinding their cursor", () => {
const first = pullRequestListHead(null, page([entry(3)], { repo: "first" }));
const continued = appendPullRequestListPage(first, page([entry(2)], { repo: "second" }), {
Expand Down Expand Up @@ -387,3 +398,40 @@ describe("pullRequestFilterChoices", () => {
expect(rows[50]).toBe("label-from-host");
});
});

describe("shown PR diff stats", () => {
const cups = EnvironmentId.make("cups");
const stat = (number: number, additions: number, deletions: number) => ({
projectId: "project" as PullRequestListEntry["projectId"],
repository: "org/repo",
number,
additions,
deletions,
});
const rows = [
{ ...entry(1), environmentId: cups },
{ ...entry(2, { additions: 4, deletions: 1 }), environmentId: cups },
];

it("keeps the same list when the answers change no row", () => {
expect(withShownPullRequestStats(rows, [])).toBe(rows);
expect(withShownPullRequestStats(rows, [{ environmentId: cups, stats: [stat(1, 0, 0)] }])).toBe(
rows,
);
expect(withShownPullRequestStats(rows, [{ environmentId: cups, stats: [stat(2, 9, 9)] }])).toBe(
rows,
);
});

it("fills only rows without stats, in their own environment", () => {
const filled = withShownPullRequestStats(rows, [
{ environmentId: EnvironmentId.make("mbp"), stats: [stat(1, 7, 7)] },
{ environmentId: cups, stats: [stat(1, 3, 2), stat(2, 9, 9)] },
]);
expect(filled.map(({ additions, deletions }) => [additions, deletions])).toEqual([
[3, 2],
[4, 1],
]);
expect(filled[1]).toBe(rows[1]);
});
});
61 changes: 61 additions & 0 deletions apps/mobile/src/features/pull-requests/pullRequestList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ProjectId,
resolvePullRequestAuthorFilter,
type EnvironmentId,
type PullRequestDiffStat,
type PullRequestListEntry,
type PullRequestListInput,
type PullRequestListResult,
Expand Down Expand Up @@ -45,6 +46,38 @@ export type EnvironmentPullRequestListEntry = PullRequestListEntry & {
export const scopedPullRequestListKey = (entry: EnvironmentPullRequestListEntry) =>
`${entry.environmentId}:${pullRequestListKey(entry)}`;

export const pullRequestStatKey = (
row: Pick<PullRequestDiffStat, "projectId" | "repository" | "number">,
) => `${row.projectId}:${row.repository}:${row.number}`;

/**
* Fills rows that have no diff stat yet from the stats answers on screen. Returns `entries`
* itself when no row changes, so the list keeps its identity across renders.
*/
export function withShownPullRequestStats(
entries: readonly EnvironmentPullRequestListEntry[],
answers: readonly {
readonly environmentId: EnvironmentId;
readonly stats: readonly PullRequestDiffStat[];
}[],
): readonly EnvironmentPullRequestListEntry[] {
const shown = new Map(
answers.flatMap(({ environmentId, stats }) =>
stats.map((row) => [`${environmentId}:${pullRequestStatKey(row)}`, row] as const),
),
);
if (!shown.size) return entries;
let changed = false;
const next = entries.map((entry) => {
const stat = shown.get(`${entry.environmentId}:${pullRequestStatKey(entry)}`);
if (!stat || entry.additions + entry.deletions !== 0) return entry;
if (stat.additions + stat.deletions === 0) return entry;
changed = true;
return { ...entry, additions: stat.additions, deletions: stat.deletions };
});
return changed ? next : entries;
}

export function pullRequestListRowsAreEqual(
previous: EnvironmentPullRequestListEntry,
next: EnvironmentPullRequestListEntry,
Expand Down Expand Up @@ -143,6 +176,34 @@ export function pullRequestListHead(
};
}

/** The recorded pages with the current list head on top; before any answer is recorded, the head alone. */
export function shownPullRequestListPages(
recorded: PullRequestListPages | null,
head: PullRequestListResult | null,
): PullRequestListPages | null {
return head ? pullRequestListHead(recorded, head) : recorded;
}

export function patchPullRequestListEntry(
recorded: PullRequestListPages | null,
head: PullRequestListResult | null,
entry: PullRequestListEntry,
patch: Partial<PullRequestListEntry>,
observedAt: number,
): PullRequestListPages | null {
const pages = shownPullRequestListPages(recorded, head);
return (
pages && {
...pages,
entries: pages.entries.map((row) =>
pullRequestListKey(row) === pullRequestListKey(entry)
? { ...row, ...patch, observedAt }
: row,
),
}
);
}

export function pullRequestListInput(
controls: PullRequestListControls,
query: string,
Expand Down
Loading
Loading