Add inline preview of fixes attached to check results

https://imgur.com/a/2paSjVH
http://screencast/cast/NTQzMzU4NzkzNDQyOTE4NHw1ZTIxYWEyMS03MQ

Release-Notes: skip
Google-Bug-Id: b/345156625
Change-Id: I905e525171f2b935806d18fcac6264fab073b56c
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-fix-preview.ts b/polygerrit-ui/app/elements/checks/gr-checks-fix-preview.ts
new file mode 100644
index 0000000..c13ef70
--- /dev/null
+++ b/polygerrit-ui/app/elements/checks/gr-checks-fix-preview.ts
@@ -0,0 +1,327 @@
+/**
+ * @license
+ * Copyright 2024 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../embed/diff/gr-diff/gr-diff';
+import {css, html, LitElement, nothing, PropertyValues} from 'lit';
+import {customElement, property, state} from 'lit/decorators.js';
+import {getAppContext} from '../../services/app-context';
+import {EDIT, BasePatchSetNum, RepoName} from '../../types/common';
+import {anyLineTooLong} from '../../utils/diff-util';
+import {
+  DiffInfo,
+  DiffLayer,
+  DiffPreferencesInfo,
+  DiffViewMode,
+  RenderPreferences,
+} from '../../api/diff';
+import {GrSyntaxLayerWorker} from '../../embed/diff/gr-syntax-layer/gr-syntax-layer-worker';
+import {resolve} from '../../models/dependency';
+import {highlightServiceToken} from '../../services/highlight/highlight-service';
+import {
+  FixSuggestionInfo,
+  NumericChangeId,
+  PatchSetNumber,
+} from '../../api/rest-api';
+import {changeModelToken} from '../../models/change/change-model';
+import {subscribe} from '../lit/subscription-controller';
+import {DiffPreview} from '../diff/gr-apply-fix-dialog/gr-apply-fix-dialog';
+import {userModelToken} from '../../models/user/user-model';
+import {navigationToken} from '../core/gr-navigation/gr-navigation';
+import {fire} from '../../utils/event-util';
+import {createChangeUrl} from '../../models/views/change';
+import {OpenFixPreviewEventDetail} from '../../types/events';
+
+/**
+ * This component renders a <gr-diff> and an "apply fix" button and can be used
+ * when showing check results that have a fix for an easy preview and a quick
+ * apply-fix experience.
+ *
+ * There is a certain overlap with similar components for comment fixes:
+ * GrSuggestionDiffPreview also renders a <gr-diff> and fetches a diff preview,
+ * it relies on a `comment` (and the comment model) to be available. It supports
+ * both a `string` fix and `FixSuggestionInfo`. It also differs in logging and
+ * event handling. And it misses the header component that we need for the
+ * buttons.
+ *
+ * There is also `GrUserSuggestionsFix` which wraps `GrSuggestionDiffPreview`
+ * and has the header that we also need. But it is very targeted to be used for
+ * user suggestions and inside comments.
+ *
+ * So there is certainly an opportunity for cleanup and unification, but at the
+ * time of component creation it did not feel wortwhile investing into this
+ * effort. This is tracked in b/360288262.
+ */
+@customElement('gr-checks-fix-preview')
+export class GrChecksFixPreview extends LitElement {
+  @property({type: Object})
+  fixSuggestionInfo?: FixSuggestionInfo;
+
+  @property({type: Number})
+  patchSet?: PatchSetNumber;
+
+  @state()
+  layers: DiffLayer[] = [];
+
+  @state()
+  repo?: RepoName;
+
+  @state()
+  changeNum?: NumericChangeId;
+
+  @state()
+  latestPatchNum?: PatchSetNumber;
+
+  @state()
+  diff?: DiffPreview;
+
+  @state()
+  applyingFix = false;
+
+  @state()
+  diffPrefs?: DiffPreferencesInfo;
+
+  @state()
+  renderPrefs: RenderPreferences = {
+    disable_context_control_buttons: true,
+    show_file_comment_button: false,
+    hide_line_length_indicator: true,
+  };
+
+  private readonly restApiService = getAppContext().restApiService;
+
+  private readonly getChangeModel = resolve(this, changeModelToken);
+
+  private readonly getUserModel = resolve(this, userModelToken);
+
+  private readonly getNavigation = resolve(this, navigationToken);
+
+  private readonly syntaxLayer = new GrSyntaxLayerWorker(
+    resolve(this, highlightServiceToken),
+    () => getAppContext().reportingService
+  );
+
+  constructor() {
+    super();
+    subscribe(
+      this,
+      () => this.getChangeModel().changeNum$,
+      changeNum => (this.changeNum = changeNum)
+    );
+    subscribe(
+      this,
+      () => this.getChangeModel().latestPatchNum$,
+      x => (this.latestPatchNum = x)
+    );
+    subscribe(
+      this,
+      () => this.getUserModel().diffPreferences$,
+      diffPreferences => {
+        if (!diffPreferences) return;
+        this.diffPrefs = diffPreferences;
+        this.syntaxLayer.setEnabled(!!this.diffPrefs.syntax_highlighting);
+      }
+    );
+    subscribe(
+      this,
+      () => this.getChangeModel().repo$,
+      x => (this.repo = x)
+    );
+  }
+
+  static override get styles() {
+    return [
+      css`
+        :host {
+          display: block;
+        }
+        .header {
+          background-color: var(--background-color-primary);
+          border: 1px solid var(--border-color);
+          border-bottom: none;
+          padding: var(--spacing-xs) var(--spacing-xl);
+          display: flex;
+          align-items: center;
+        }
+        .header .title {
+          flex: 1;
+        }
+        .diff-container {
+          border: 1px solid var(--border-color);
+          border-top: none;
+          border-bottom: none;
+        }
+        .loading {
+          border: 1px solid var(--border-color);
+          padding: var(--spacing-xl);
+        }
+      `,
+    ];
+  }
+
+  override willUpdate(changed: PropertyValues) {
+    if (changed.has('fixSuggestionInfo')) {
+      this.fetchDiffPreview().then(diff => (this.diff = diff));
+    }
+  }
+
+  override render() {
+    if (!this.fixSuggestionInfo) return nothing;
+    return html`${this.renderHeader()}${this.renderDiff()}`;
+  }
+
+  private renderHeader() {
+    return html`
+      <div class="header">
+        <div class="title">
+          <span>Attached Fix</span>
+        </div>
+        <div>
+          <gr-button
+            class="showFix"
+            secondary
+            flatten
+            .disabled=${!this.diff}
+            @click=${this.showFix}
+          >
+            Show fix side-by-side
+          </gr-button>
+          <gr-button
+            class="applyFix"
+            primary
+            flatten
+            .loading=${this.applyingFix}
+            .disabled=${this.isApplyEditDisabled()}
+            @click=${this.applyFix}
+            .title=${this.computeApplyFixTooltip()}
+          >
+            Apply fix
+          </gr-button>
+        </div>
+      </div>
+    `;
+  }
+
+  private renderDiff() {
+    if (!this.diff) {
+      return html`<div class="loading">Loading fix preview ...</div>`;
+    }
+    const diff = this.diff.preview;
+    if (!anyLineTooLong(diff)) {
+      this.syntaxLayer.process(diff);
+    }
+    return html`
+      <div class="diff-container">
+        <gr-diff
+          .prefs=${this.getDiffPrefs()}
+          .path=${this.diff.filepath}
+          .diff=${diff}
+          .layers=${this.layers}
+          .renderPrefs=${this.renderPrefs}
+          .viewMode=${DiffViewMode.UNIFIED}
+        ></gr-diff>
+      </div>
+    `;
+  }
+
+  /**
+   * Calls the REST API to convert the fix into a DiffInfo.
+   */
+  private async fetchDiffPreview(): Promise<DiffPreview | undefined> {
+    if (!this.changeNum || !this.patchSet || !this.fixSuggestionInfo) return;
+    const pathsToDiffs: {[path: string]: DiffInfo} | undefined =
+      await this.restApiService.getFixPreview(
+        this.changeNum,
+        this.patchSet,
+        this.fixSuggestionInfo.replacements
+      );
+
+    if (!pathsToDiffs) return;
+    const diffs = Object.keys(pathsToDiffs).map(filepath => {
+      const diff = pathsToDiffs[filepath];
+      return {filepath, preview: diff};
+    });
+    // Showing diff for one file only.
+    return diffs?.[0];
+  }
+
+  private showFix() {
+    if (!this.patchSet || !this.fixSuggestionInfo) return;
+    const eventDetail: OpenFixPreviewEventDetail = {
+      patchNum: this.patchSet,
+      fixSuggestions: [this.fixSuggestionInfo],
+      onCloseFixPreviewCallbacks: [],
+    };
+    fire(this, 'open-fix-preview', eventDetail);
+  }
+
+  /**
+   * Applies the fix and then navigates to the EDIT patchset.
+   */
+  private async applyFix() {
+    const changeNum = this.changeNum;
+    const basePatchNum = this.patchSet as BasePatchSetNum;
+    if (!changeNum || !basePatchNum || !this.fixSuggestionInfo) return;
+
+    this.applyingFix = true;
+    const res = await this.restApiService.applyFixSuggestion(
+      changeNum,
+      basePatchNum,
+      this.fixSuggestionInfo.replacements
+    );
+    this.applyingFix = false;
+    if (res?.ok) this.navigateToEditPatchset();
+  }
+
+  private navigateToEditPatchset() {
+    const changeNum = this.changeNum;
+    const repo = this.repo;
+    const basePatchNum = this.patchSet;
+    if (!changeNum || !repo || !basePatchNum) return;
+
+    const url = createChangeUrl({
+      changeNum,
+      repo,
+      patchNum: EDIT,
+      basePatchNum,
+      // We have to force reload, because the EDIT patchset is otherwise not yet known.
+      forceReload: true,
+    });
+    this.getNavigation().setUrl(url);
+  }
+
+  /**
+   * We have to override some diff prefs of the user, because for example in the context of showing
+   * an inline diff for fixes we do not want to show context lines around the changes lines of code
+   * as we would normally do for a diff.
+   */
+  private getDiffPrefs() {
+    if (!this.diffPrefs) return undefined;
+    return {
+      ...this.diffPrefs,
+      context: 0,
+      line_length: Math.min(this.diffPrefs.line_length, 100),
+      line_wrapping: true,
+    };
+  }
+
+  private isApplyEditDisabled() {
+    if (!this.diff || this.patchSet === undefined) return true;
+    return this.patchSet !== this.latestPatchNum;
+  }
+
+  private computeApplyFixTooltip() {
+    if (this.patchSet === undefined) return '';
+    if (!this.diff) return 'Fix is still loading ...';
+    return this.patchSet !== this.latestPatchNum
+      ? 'You cannot apply this fix because it is from a previous patchset'
+      : '';
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-checks-fix-preview': GrChecksFixPreview;
+  }
+}
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-fix-preview_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-fix-preview_test.ts
new file mode 100644
index 0000000..21fa5d6
--- /dev/null
+++ b/polygerrit-ui/app/elements/checks/gr-checks-fix-preview_test.ts
@@ -0,0 +1,171 @@
+/**
+ * @license
+ * Copyright 2024 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../test/common-test-setup';
+import './gr-checks-results';
+import {html} from 'lit';
+import {fixture, assert} from '@open-wc/testing';
+import {createCheckFix, createDiff} from '../../test/test-data-generators';
+import {GrChecksFixPreview} from './gr-checks-fix-preview';
+import {rectifyFix} from '../../models/checks/checks-util';
+import {
+  MockPromise,
+  mockPromise,
+  queryAndAssert,
+  stubRestApi,
+  waitUntil,
+} from '../../test/test-utils';
+import {NumericChangeId, PatchSetNumber, RepoName} from '../../api/rest-api';
+import {FilePathToDiffInfoMap} from '../../types/common';
+import {testResolver} from '../../test/common-test-setup';
+import {navigationToken} from '../core/gr-navigation/gr-navigation';
+
+suite('gr-checks-fix-preview test', () => {
+  let element: GrChecksFixPreview;
+  let promise: MockPromise<FilePathToDiffInfoMap | undefined>;
+
+  setup(async () => {
+    promise = mockPromise<FilePathToDiffInfoMap | undefined>();
+    stubRestApi('getFixPreview').returns(promise);
+
+    const fix = rectifyFix(createCheckFix(), 'test-checker');
+    element = await fixture<GrChecksFixPreview>(
+      html`<gr-checks-fix-preview></gr-checks-fix-preview>`
+    );
+    await element.updateComplete;
+
+    element.changeNum = 123 as NumericChangeId;
+    element.patchSet = 5 as PatchSetNumber;
+    element.latestPatchNum = 5 as PatchSetNumber;
+    element.repo = 'test-repo' as RepoName;
+    element.fixSuggestionInfo = fix;
+    await element.updateComplete;
+  });
+
+  const loadDiff = async () => {
+    promise.resolve({'foo.c': createDiff()});
+    await waitUntil(() => !!element.diff);
+  };
+
+  test('renders loading', async () => {
+    assert.shadowDom.equal(
+      element,
+      /* HTML */ `
+        <div class="header">
+          <div class="title">
+            <span> Attached Fix </span>
+          </div>
+          <div>
+            <gr-button
+              class="showFix"
+              aria-disabled="true"
+              disabled=""
+              flatten=""
+              role="button"
+              secondary=""
+              tabindex="-1"
+            >
+              Show fix side-by-side
+            </gr-button>
+            <gr-button
+              class="applyFix"
+              aria-disabled="true"
+              disabled=""
+              flatten=""
+              primary=""
+              role="button"
+              tabindex="-1"
+              title="Fix is still loading ..."
+            >
+              Apply fix
+            </gr-button>
+          </div>
+        </div>
+        <div class="loading">Loading fix preview ...</div>
+      `
+    );
+  });
+
+  test('renders diff', async () => {
+    await loadDiff();
+    assert.shadowDom.equal(
+      element,
+      /* HTML */ `
+        <div class="header">
+          <div class="title">
+            <span> Attached Fix </span>
+          </div>
+          <div>
+            <gr-button
+              class="showFix"
+              aria-disabled="false"
+              flatten=""
+              role="button"
+              secondary=""
+              tabindex="0"
+            >
+              Show fix side-by-side
+            </gr-button>
+            <gr-button
+              class="applyFix"
+              aria-disabled="false"
+              flatten=""
+              primary=""
+              role="button"
+              tabindex="0"
+              title=""
+            >
+              Apply fix
+            </gr-button>
+          </div>
+        </div>
+        <div class="diff-container">
+          <gr-diff
+            class="disable-context-control-buttons hide-line-length-indicator"
+            style="--line-limit-marker: 100ch; --content-width: none; --diff-max-width: none; --font-size: 12px;"
+          >
+          </gr-diff>
+        </div>
+      `
+    );
+  });
+
+  test('show-fix', async () => {
+    await loadDiff();
+
+    const stub = sinon.stub();
+    element.addEventListener('open-fix-preview', stub);
+
+    const button = queryAndAssert<HTMLElement>(element, 'gr-button.showFix');
+    assert.isFalse(button.hasAttribute('disabled'));
+    button.click();
+
+    assert.isTrue(stub.called);
+    assert.deepEqual(stub.lastCall.args[0].detail, {
+      patchNum: element.patchSet,
+      fixSuggestions: [element.fixSuggestionInfo],
+      onCloseFixPreviewCallbacks: [],
+    });
+  });
+
+  test('apply-fix', async () => {
+    await loadDiff();
+
+    const setUrlSpy = sinon.stub(testResolver(navigationToken), 'setUrl');
+    stubRestApi('applyFixSuggestion').returns(
+      Promise.resolve({ok: true} as Response)
+    );
+
+    const button = queryAndAssert<HTMLElement>(element, 'gr-button.applyFix');
+    assert.isFalse(button.hasAttribute('disabled'));
+    button.click();
+
+    await waitUntil(() => setUrlSpy.called);
+    assert.equal(
+      setUrlSpy.lastCall.args[0],
+      '/c/test-repo/+/123/5..edit?forceReload=true'
+    );
+  });
+});
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 6cff5ab..b179e98 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -48,6 +48,7 @@
   secondaryLinks,
   tooltipForLink,
   computeIsExpandable,
+  rectifyFix,
 } from '../../models/checks/checks-util';
 import {assertIsDefined, assert, unique} from '../../utils/common-util';
 import {modifierPressed, whenVisible} from '../../utils/dom-util';
@@ -79,6 +80,7 @@
 import {when} from 'lit/directives/when.js';
 import {DropdownItem} from '../shared/gr-dropdown-list/gr-dropdown-list';
 import './gr-checks-attempt';
+import './gr-checks-fix-preview';
 import {changeViewModelToken} from '../../models/views/change';
 import {formStyles} from '../../styles/form-styles';
 
@@ -562,8 +564,11 @@
 
   private renderActions() {
     const actions = [...(this.result?.actions ?? [])];
-    const fixAction = createFixAction(this, this.result);
-    if (fixAction) actions.unshift(fixAction);
+    let fixAction: Action | undefined = undefined;
+    if (!this.isExpanded) {
+      fixAction = createFixAction(this, this.result);
+      if (fixAction) actions.unshift(fixAction);
+    }
     if (actions.length === 0) return;
     const overflowItems = actions.slice(2).map(action => {
       return {...action, id: action.name};
@@ -657,6 +662,10 @@
         .message {
           padding: var(--spacing-m) 0;
         }
+        gr-checks-fix-preview {
+          margin: var(--spacing-l) 0;
+          max-width: 800px;
+        }
       `,
     ];
   }
@@ -681,6 +690,7 @@
           .content=${this.result.message ?? ''}
         ></gr-formatted-text>
       </gr-endpoint-decorator>
+      ${this.renderFix()}
     `;
   }
 
@@ -738,6 +748,20 @@
     );
   }
 
+  private renderFix() {
+    const fixSuggestionInfo = rectifyFix(
+      this.result?.fixes?.[0],
+      this.result?.checkName
+    );
+    if (!fixSuggestionInfo) return;
+    return html`
+      <gr-checks-fix-preview
+        .fixSuggestionInfo=${fixSuggestionInfo}
+        .patchSet=${this.result?.patchset}
+      ></gr-checks-fix-preview>
+    `;
+  }
+
   private renderLink(link?: Link, targetBlank = true) {
     if (!link) return;
     const text = link.tooltip ?? tooltipForLink(link.icon);
diff --git a/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts b/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
index 89c7fd7..16d3d3e 100644
--- a/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
+++ b/polygerrit-ui/app/elements/checks/gr-diff-check-result.ts
@@ -236,6 +236,7 @@
   }
 
   private renderShowFixButton() {
+    if (this.isExpanded) return nothing;
     const action = createFixAction(this, this.result);
     if (!action) return nothing;
     return html`
diff --git a/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts b/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts
index 5aa266c..248dd62 100644
--- a/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-diff-check-result_test.ts
@@ -83,10 +83,6 @@
           <gr-result-expanded hidecodepointers=""></gr-result-expanded>
           <div class="actions">
             <gr-checks-action
-              id="show-fix"
-              context="diff-fix"
-            ></gr-checks-action>
-            <gr-checks-action
               id="please-fix"
               context="diff-fix"
             ></gr-checks-action>
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index 977ac2e..7c224a9 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -43,7 +43,7 @@
 import {changeModelToken} from '../../../models/change/change-model';
 import {getFileExtension} from '../../../utils/file-util';
 
-export interface FilePreview {
+export interface DiffPreview {
   filepath: string;
   preview: DiffInfo;
 }
@@ -80,7 +80,7 @@
   currentFix?: FixSuggestionInfo;
 
   @state()
-  currentPreviews: FilePreview[] = [];
+  currentPreviews: DiffPreview[] = [];
 
   @state()
   fixSuggestions?: FixSuggestionInfo[];
@@ -233,7 +233,7 @@
     return html`<div slot="main">${items}</div>`;
   }
 
-  private renderDiff(preview: FilePreview) {
+  private renderDiff(preview: DiffPreview) {
     const diff = preview.preview;
     if (!anyLineTooLong(diff)) {
       this.syntaxLayer.process(diff);
diff --git a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
index e1d4f89..48328ea 100644
--- a/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
+++ b/polygerrit-ui/app/elements/shared/gr-suggestion-diff-preview/gr-suggestion-diff-preview.ts
@@ -22,7 +22,7 @@
 import {FixSuggestionInfo, NumericChangeId} from '../../../api/rest-api';
 import {changeModelToken} from '../../../models/change/change-model';
 import {subscribe} from '../../lit/subscription-controller';
-import {FilePreview} from '../../diff/gr-apply-fix-dialog/gr-apply-fix-dialog';
+import {DiffPreview} from '../../diff/gr-apply-fix-dialog/gr-apply-fix-dialog';
 import {userModelToken} from '../../../models/user/user-model';
 import {createUserFixSuggestion} from '../../../utils/comment-util';
 import {commentModelToken} from '../gr-comment-model/gr-comment-model';
@@ -87,7 +87,7 @@
   changeNum?: NumericChangeId;
 
   @state()
-  preview?: FilePreview;
+  preview?: DiffPreview;
 
   @state()
   diffPrefs?: DiffPreferencesInfo;
diff --git a/polygerrit-ui/app/models/checks/checks-fakes.ts b/polygerrit-ui/app/models/checks/checks-fakes.ts
index 1890639..cafe510 100644
--- a/polygerrit-ui/app/models/checks/checks-fakes.ts
+++ b/polygerrit-ui/app/models/checks/checks-fakes.ts
@@ -90,7 +90,7 @@
   checkName: 'FAKE Super Check',
   startedTimestamp: new Date(new Date().getTime() - 5 * 60 * 1000),
   finishedTimestamp: new Date(new Date().getTime() + 5 * 60 * 1000),
-  patchset: 1,
+  patchset: 3,
   labelName: 'Verified',
   isSingleAttempt: true,
   isLatestAttempt: true,
@@ -159,16 +159,16 @@
     {
       internalResultId: 'f1r2',
       category: Category.ERROR,
-      summary: 'Suspicious Date',
-      message: 'That was a holiday, you know.',
+      summary: 'Test Size Checker',
+      message: 'The test seems to be of large size, not medium.',
       codePointers: [
         {
-          path: '/COMMIT_MSG',
+          path: 'plugins/BUILD',
           range: {
-            start_line: 3,
-            start_character: 0,
-            end_line: 3,
-            end_character: 0,
+            start_line: 186,
+            start_character: 12,
+            end_line: 186,
+            end_character: 18,
           },
         },
       ],
@@ -177,14 +177,14 @@
           description: 'This is the way to do it.',
           replacements: [
             {
-              path: 'BUILD',
+              path: 'plugins/BUILD',
               range: {
-                start_line: 1,
-                start_character: 0,
-                end_line: 1,
-                end_character: 0,
+                start_line: 186,
+                start_character: 12,
+                end_line: 186,
+                end_character: 18,
               },
-              replacement: '# This is now fixed.\n',
+              replacement: 'large',
             },
           ],
         },
diff --git a/polygerrit-ui/app/models/checks/checks-util.ts b/polygerrit-ui/app/models/checks/checks-util.ts
index fc18968..51bfcd9 100644
--- a/polygerrit-ui/app/models/checks/checks-util.ts
+++ b/polygerrit-ui/app/models/checks/checks-util.ts
@@ -136,9 +136,9 @@
 
 export function rectifyFix(
   fix: Fix | undefined,
-  checkName: string
+  checkName: string | undefined
 ): FixSuggestionInfo | undefined {
-  if (!fix?.replacements) return undefined;
+  if (!fix?.replacements || !checkName) return undefined;
   const replacements = fix.replacements
     .map(rectifyReplacement)
     .filter(isDefined);