From 09f8806b461ca603da11851192dc3c630c8e22e8 Mon Sep 17 00:00:00 2001 From: Viktor Sharha Date: Fri, 11 Sep 2026 17:55:58 +0100 Subject: [PATCH 1/2] Fix mergeability polling in pull request overview --- src/github/interface.ts | 5 +++++ src/github/pullRequestModel.ts | 3 ++- webviews/common/context.tsx | 5 +++-- webviews/components/merge.tsx | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/github/interface.ts b/src/github/interface.ts index 30fa68149d..b5338b8449 100644 --- a/src/github/interface.ts +++ b/src/github/interface.ts @@ -31,6 +31,11 @@ export enum PullRequestMergeability { Behind, } +export interface PullRequestMergeabilityResult { + mergeability: PullRequestMergeability; + conflicts?: string[]; +} + export enum MergeQueueState { AwaitingChecks, Locked, diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index c18fad70b5..6a3bc8e6b7 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -62,6 +62,7 @@ import { PullRequest, PullRequestChecks, PullRequestMergeability, + PullRequestMergeabilityResult, PullRequestReviewRequirement, ReadyForReview, ReviewEventEnum, @@ -2055,7 +2056,7 @@ export class PullRequestModel extends IssueModel implements IPullRe /** * Get the current mergeability of the pull request. */ - async getMergeability(): Promise<{ mergeability: PullRequestMergeability, conflicts?: string[] }> { + async getMergeability(): Promise { try { Logger.debug(`Fetch pull request mergeability ${this.number} - enter`, PullRequestModel.ID); const { query, remote, schema } = await this.githubRepository.ensure(); diff --git a/webviews/common/context.tsx b/webviews/common/context.tsx index 73e3aae5cf..7da83b58b3 100644 --- a/webviews/common/context.tsx +++ b/webviews/common/context.tsx @@ -10,7 +10,7 @@ import { getMessageHandler, MessageHandler } from './message'; import { CloseResult, DescriptionResult, OpenCommitChangesArgs, OpenLocalFileArgs } from '../../common/views'; import { IComment } from '../../src/common/comment'; import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../../src/common/timelineEvent'; -import { IProjectItem, MergeMethod, PullRequestCheckStatus, ReadyForReview } from '../../src/github/interface'; +import { IProjectItem, MergeMethod, PullRequestCheckStatus, PullRequestMergeabilityResult, ReadyForReview } from '../../src/github/interface'; import { CancelCodingAgentReply, ChangeAssigneesReply, ChangeBaseReply, ConvertToDraftReply, DeleteReviewResult, FileUploadCompletedMessage, MergeArguments, MergeResult, ProjectItemsReply, PullRequest, ReadyForReviewReply, SubmitReviewArgs, SubmitReviewReply, UploadFilesReply } from '../../src/github/views'; /** @@ -86,7 +86,8 @@ export class PRContext { this.updatePR(this.pr); }; - public checkMergeability = () => this.postMessage({ command: 'pr.checkMergeability' }); + public checkMergeability = (): Promise => + this.postMessage({ command: 'pr.checkMergeability' }); public changeEmail = async (current: string) => { const newEmail = await this.postMessage({ command: 'pr.change-email', args: current }); diff --git a/webviews/components/merge.tsx b/webviews/components/merge.tsx index c8d7d31ced..29e72de680 100644 --- a/webviews/components/merge.tsx +++ b/webviews/components/merge.tsx @@ -192,7 +192,7 @@ export const MergeStatusAndActions = ({ pr, isSimple }: { pr: PullRequest; isSim useEffect(() => { const handle = setInterval(async () => { if (mergeable === PullRequestMergeability.Unknown) { - const newMergeability = await checkMergeability(); + const { mergeability: newMergeability } = await checkMergeability(); setMergeability(newMergeability); } }, 3000); From 5c98941a6406215d797ec689a232079aceea40fd Mon Sep 17 00:00:00 2001 From: Viktor Sharha Date: Fri, 11 Sep 2026 18:41:42 +0100 Subject: [PATCH 2/2] Add test for unknown mergeability polling transition --- webviews/editorWebview/test/merge.test.tsx | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 webviews/editorWebview/test/merge.test.tsx diff --git a/webviews/editorWebview/test/merge.test.tsx b/webviews/editorWebview/test/merge.test.tsx new file mode 100644 index 0000000000..282bd0d840 --- /dev/null +++ b/webviews/editorWebview/test/merge.test.tsx @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { default as assert } from 'assert'; +import * as React from 'react'; +import { cleanup, render } from 'react-testing-library'; +import { createSandbox, SinonFakeTimers, SinonSandbox } from 'sinon'; + +import { PullRequestBuilder } from './builder/pullRequest'; +import { PullRequestMergeability } from '../../../src/github/interface'; +import { PRContext, default as PullRequestContext } from '../../common/context'; +import { MergeStatusAndActions } from '../../components/merge'; + +describe('Merge status and actions', function () { + let sinon: SinonSandbox; + let clock: SinonFakeTimers & { tickAsync(milliseconds: number): Promise }; + + beforeEach(function () { + sinon = createSandbox(); + clock = sinon.useFakeTimers() as SinonFakeTimers & { tickAsync(milliseconds: number): Promise }; + }); + + afterEach(function () { + cleanup(); + sinon.restore(); + }); + + it('updates an unknown mergeability state from the polling response', async function () { + const pr = new PullRequestBuilder() + .mergeable(PullRequestMergeability.Unknown) + .hasWritePermission(false) + .build(); + const context = new PRContext(pr); + const checkMergeability = sinon.stub(context, 'checkMergeability').resolves({ + mergeability: PullRequestMergeability.Mergeable, + }); + + const view = render( + + + , + ); + + assert(view.queryByText('Checking if this branch can be merged...')); + + await clock.tickAsync(3000); + + assert(view.queryByText('This branch has no conflicts with the base branch.')); + assert.strictEqual(checkMergeability.calledOnce, true); + + await clock.tickAsync(3000); + + assert.strictEqual(checkMergeability.calledOnce, true); + }); +});