diff --git a/webviews/common/common.css b/webviews/common/common.css
index e627c12ff9..891b88e33c 100644
--- a/webviews/common/common.css
+++ b/webviews/common/common.css
@@ -376,6 +376,10 @@ body img.avatar {
margin-right: 0;
}
+.status-check-link-placeholder {
+ visibility: hidden;
+}
+
.automerge-section {
display: flex;
}
diff --git a/webviews/components/merge.tsx b/webviews/components/merge.tsx
index b13faf6ca2..edb7fa16c9 100644
--- a/webviews/components/merge.tsx
+++ b/webviews/components/merge.tsx
@@ -705,14 +705,12 @@ const StatusCheckDetails = ({ statuses }: { statuses: PullRequestCheckStatus[] }
Details
- ) : null}
- {s.isCheckRun && s.databaseId ? (
- s.state === CheckState.Failure ? (
-
- ) :
- ) : null}
+ ) : Details}
+ {s.isCheckRun && s.databaseId && s.state === CheckState.Failure ? (
+
+ ) : }
))}
diff --git a/webviews/editorWebview/index.css b/webviews/editorWebview/index.css
index 90b10eccd0..6877797ca3 100644
--- a/webviews/editorWebview/index.css
+++ b/webviews/editorWebview/index.css
@@ -380,8 +380,9 @@ body .comment-container .review-comment-header {
gap: 4px;
}
+.status-check .icon-button,
.view-check-logs-placeholder {
- width: 20px;
+ flex: 0 0 22px;
}
#merge-on-github {
diff --git a/webviews/editorWebview/test/overview.test.tsx b/webviews/editorWebview/test/overview.test.tsx
index b8d4243c8d..cc30d6280f 100644
--- a/webviews/editorWebview/test/overview.test.tsx
+++ b/webviews/editorWebview/test/overview.test.tsx
@@ -4,14 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { default as assert } from 'assert';
+import { readFileSync } from 'fs';
+import * as path from 'path';
import * as React from 'react';
import { cleanup, fireEvent, render, wait, waitForElement } from 'react-testing-library';
import { createSandbox, SinonSandbox } from 'sinon';
-import { GithubItemStateEnum, PullRequestMergeability } from '../../../src/github/interface';
+import { PullRequestBuilder } from './builder/pullRequest';
+import { CheckState, GithubItemStateEnum, PullRequestCheckStatus, PullRequestMergeability } from '../../../src/github/interface';
+import { Overview as ActivityBarOverview } from '../../activityBarView/overview';
import { PRContext, default as PullRequestContext } from '../../common/context';
import { Overview } from '../overview';
-import { PullRequestBuilder } from './builder/pullRequest';
describe('Overview', function () {
let sinon: SinonSandbox;
@@ -60,6 +63,106 @@ describe('Overview', function () {
assert.strictEqual(openOnGitHub.callCount, 2);
});
+ it('reserves details and log action slots for every status check', async function () {
+ const cases: Pick[] = [
+ { state: CheckState.Failure, isCheckRun: false, databaseId: undefined },
+ { state: CheckState.Failure, isCheckRun: true, databaseId: 1 },
+ { state: CheckState.Pending, isCheckRun: true, databaseId: 2 },
+ { state: CheckState.Success, isCheckRun: true, databaseId: 3 },
+ { state: CheckState.Neutral, isCheckRun: true, databaseId: 4 },
+ { state: CheckState.Unknown, isCheckRun: true, databaseId: 5 },
+ { state: CheckState.Failure, isCheckRun: true, databaseId: null },
+ { state: CheckState.Failure, isCheckRun: true, databaseId: undefined },
+ { state: CheckState.Failure, isCheckRun: true, databaseId: 0 },
+ ];
+ const statuses: PullRequestCheckStatus[] = cases.map((check, index) => ({
+ ...check,
+ id: `check-${index}`,
+ context: `Check ${index}`,
+ description: null,
+ workflowName: undefined,
+ event: undefined,
+ url: undefined,
+ avatarUrl: undefined,
+ targetUrl: index === cases.length - 1 ? null : `https://example.com/checks/${index}`,
+ isRequired: index % 2 === 0,
+ }));
+ const pr = new PullRequestBuilder().status(status => status.state(CheckState.Failure).statuses(statuses)).build();
+ const context = new PRContext(pr);
+ const viewCheckLogs = sinon.stub(context, 'viewCheckLogs').resolves();
+ const out = render(
+
+
+ ,
+ );
+
+ const rows = out.container.querySelectorAll('.status-check');
+ assert.strictEqual(rows.length, statuses.length);
+ for (const status of statuses) {
+ const row = [...rows].find(row => row.querySelector('.status-check-detail-text')?.textContent?.trim() === status.context);
+ assert(row);
+ const actions = row.lastElementChild;
+ assert(actions);
+ assert.strictEqual(actions.querySelector('.label')?.textContent ?? null, status.isRequired ? 'Required' : null);
+ assert.strictEqual(actions.querySelector('a')?.getAttribute('href') ?? null, status.targetUrl);
+ const linkPlaceholder = actions.querySelector('.status-check-link-placeholder');
+ if (status.targetUrl) {
+ assert.strictEqual(linkPlaceholder, null);
+ } else {
+ assert(linkPlaceholder);
+ assert.strictEqual(linkPlaceholder.textContent, 'Details');
+ assert.strictEqual(linkPlaceholder.getAttribute('aria-hidden'), 'true');
+ }
+ const slot = actions.lastElementChild;
+ assert(slot);
+ if (status.isCheckRun && status.databaseId && status.state === CheckState.Failure) {
+ assert.strictEqual(slot.getAttribute('title'), 'View Logs');
+ assert.strictEqual(actions.querySelector('.view-check-logs-placeholder'), null);
+ fireEvent.click(slot);
+ await wait(() => assert(viewCheckLogs.calledOnceWithExactly(status)));
+ } else {
+ assert(slot.classList.contains('view-check-logs-placeholder'));
+ assert.strictEqual(slot.getAttribute('aria-hidden'), 'true');
+ assert.strictEqual(actions.querySelector('button'), null);
+ }
+ }
+ assert.strictEqual(viewCheckLogs.callCount, 1);
+ });
+
+ it('keeps Details placeholders invisible in both PR overviews using shared styles', function () {
+ const status: PullRequestCheckStatus = {
+ id: 'missing-details', state: CheckState.Failure, context: 'Check without details',
+ description: null, targetUrl: null, workflowName: undefined, event: undefined,
+ url: undefined, avatarUrl: undefined, isRequired: false, isCheckRun: false, databaseId: undefined,
+ };
+ const pr = new PullRequestBuilder().status(checks => checks.state(CheckState.Failure).statuses([status])).build();
+ const sharedCss = readFileSync(path.resolve('webviews', 'common', 'common.css'), 'utf8');
+ const placeholderRule = /^\.status-check-link-placeholder\s*\{[^}]*\}/m.exec(sharedCss);
+ assert(placeholderRule);
+ const sharedStyles = document.createElement('style');
+ sharedStyles.textContent = placeholderRule[0];
+ document.head.appendChild(sharedStyles);
+ try {
+ for (const Component of [Overview, ActivityBarOverview]) {
+ const out = render(
+
+
+ ,
+ );
+ const placeholder = out.container.querySelector('.status-check-link-placeholder');
+ assert(placeholder);
+ assert.strictEqual(placeholder.textContent, 'Details');
+ assert.strictEqual(placeholder.getAttribute('aria-hidden'), 'true');
+ const style = window.getComputedStyle(placeholder);
+ assert.strictEqual(style.visibility, 'hidden');
+ assert.notStrictEqual(style.display, 'none');
+ out.unmount();
+ }
+ } finally {
+ sharedStyles.remove();
+ }
+ });
+
it('shows the stack position and ordered pull requests in the merge section', async function () {
const pr = new PullRequestBuilder().number(794).stack({
position: 2,