text: import MockAdapter from 'axios-mock-adapter';
  import Cookies from 'js-cookie';
  import { useLocalStorageSpy } from 'helpers/local_storage_helper';
  import { TEST_HOST } from 'helpers/test_constants';
  import testAction from 'helpers/vuex_action_helper';
  import mockDiffFile from 'jest/diffs/mock_data/diff_file';
  import {
    DIFF_VIEW_COOKIE_NAME,
    INLINE_DIFF_VIEW_TYPE,
    PARALLEL_DIFF_VIEW_TYPE,
  } from '~/diffs/constants';
  import {
    setBaseConfig,
    fetchDiffFilesBatch,
    fetchDiffFilesMeta,
    fetchCoverageFiles,
    assignDiscussionsToDiff,
    removeDiscussionsFromDiff,
    startRenderDiffsQueue,
    setInlineDiffViewType,
    setParallelDiffViewType,
    showCommentForm,
    cancelCommentForm,
    loadMoreLines,
    scrollToLineIfNeededInline,
    scrollToLineIfNeededParallel,
    loadCollapsedDiff,
    toggleFileDiscussions,
    saveDiffDiscussion,
    setHighlightedRow,
    toggleTreeOpen,
    scrollToFile,
    setShowTreeList,
    renderFileForDiscussionId,
    setRenderTreeList,
    setShowWhitespace,
    setRenderIt,
    receiveFullDiffError,
    fetchFullDiff,
    toggleFullDiff,
    switchToFullDiffFromRenamedFile,
    setFileCollapsedByUser,
    setExpandedDiffLines,
    setSuggestPopoverDismissed,
    changeCurrentCommit,
    moveToNeighboringCommit,
    setCurrentDiffFileIdFromNote,
    navigateToDiffFileIndex,
    setFileByFile,
    reviewFile,
  } from '~/diffs/store/actions';
  import * as types from '~/diffs/store/mutation_types';
  import * as utils from '~/diffs/store/utils';
  import * as treeWorkerUtils from '~/diffs/utils/tree_worker_utils';
  import createFlash from '~/flash';
  import axios from '~/lib/utils/axios_utils';
  import * as commonUtils from '~/lib/utils/common_utils';
  import { mergeUrlParams } from '~/lib/utils/url_utility';
  import eventHub from '~/notes/event_hub';
  import { diffMetadata } from '../mock_data/diff_metadata';
  
  jest.mock('~/flash');
  
  describe('DiffsStoreActions', () => {
    useLocalStorageSpy();
  
    const originalMethods = {
      requestAnimationFrame: global.requestAnimationFrame,
      requestIdleCallback: global.requestIdleCallback,
    };
  
    beforeEach(() => {
      jest.spyOn(window.history, 'pushState');
      jest.spyOn(commonUtils, 'historyPushState');
      jest.spyOn(commonUtils, 'handleLocationHash').mockImplementation(() => null);
      jest.spyOn(commonUtils, 'scrollToElement').mockImplementation(() => null);
      jest.spyOn(utils, 'convertExpandLines').mockImplementation(() => null);
      jest.spyOn(utils, 'idleCallback').mockImplementation(() => null);
      ['requestAnimationFrame', 'requestIdleCallback'].forEach((method) => {
        global[method] = (cb) => {
          cb({ timeRemaining: () => 10 });
        };
      });
    });
  
    afterEach(() => {
      ['requestAnimationFrame', 'requestIdleCallback'].forEach((method) => {
        global[method] = originalMethods[method];
      });
      createFlash.mockClear();
    });
  
    describe('setBaseConfig', () => {
      it('should set given endpoint and project path', (done) => {
        const endpoint = '/diffs/set/endpoint';
        const endpointMetadata = '/diffs/set/endpoint/metadata';
        const endpointBatch = '/diffs/set/endpoint/batch';
        const endpointCoverage = '/diffs/set/coverage_reports';
        const projectPath = '/root/project';
        const dismissEndpoint = '/-/user_callouts';
        const showSuggestPopover = false;
  
        testAction(
          setBaseConfig,
          {
            endpoint,
            endpointBatch,
            endpointMetadata,
            endpointCoverage,
            projectPath,
            dismissEndpoint,
            showSuggestPopover,
          },
          {
            endpoint: '',
            endpointBatch: '',
            endpointMetadata: '',
            endpointCoverage: '',
            projectPath: '',
            dismissEndpoint: '',
            showSuggestPopover: true,
          },
          [
            {
              type: types.SET_BASE_CONFIG,
              payload: {
                endpoint,
                endpointMetadata,
                endpointBatch,
                endpointCoverage,
                projectPath,
                dismissEndpoint,
                showSuggestPopover,
              },
            },
          ],
          [],
          done,
        );
      });
    });
  
    describe('fetchDiffFilesBatch', () => {
      let mock;
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
      });
  
      afterEach(() => {
        mock.restore();
      });
  
      it('should fetch batch diff files', (done) => {
        const endpointBatch = '/fetch/diffs_batch';
        const res1 = { diff_files: [{ file_hash: 'test' }], pagination: { total_pages: 7 } };
        const res2 = { diff_files: [{ file_hash: 'test2' }], pagination: { total_pages: 7 } };
        mock
          .onGet(
            mergeUrlParams(
              {
                w: '1',
                view: 'inline',
                page: 0,
                per_page: 5,
              },
              endpointBatch,
            ),
          )
          .reply(200, res1)
          .onGet(
            mergeUrlParams(
              {
                w: '1',
                view: 'inline',
                page: 5,
                per_page: 7,
              },
              endpointBatch,
            ),
          )
          .reply(200, res2);
  
        testAction(
          fetchDiffFilesBatch,
          {},
          { endpointBatch, diffViewType: 'inline' },
          [
            { type: types.SET_BATCH_LOADING_STATE, payload: 'loading' },
            { type: types.SET_RETRIEVING_BATCHES, payload: true },
            { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res1.diff_files } },
            { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' },
            { type: types.VIEW_DIFF_FILE, payload: 'test' },
            { type: types.SET_DIFF_DATA_BATCH, payload: { diff_files: res2.diff_files } },
            { type: types.SET_BATCH_LOADING_STATE, payload: 'loaded' },
            { type: types.VIEW_DIFF_FILE, payload: 'test2' },
            { type: types.SET_RETRIEVING_BATCHES, payload: false },
            { type: types.SET_BATCH_LOADING_STATE, payload: 'error' },
          ],
          [{ type: 'startRenderDiffsQueue' }, { type: 'startRenderDiffsQueue' }],
          done,
        );
      });
  
      it.each`
        viewStyle     | otherView
        ${'inline'}   | ${'parallel'}
        ${'parallel'} | ${'inline'}
      `(
        'should make a request with the view parameter "$viewStyle" when the batchEndpoint already contains "$otherView"',
        ({ viewStyle, otherView }) => {
          const endpointBatch = '/fetch/diffs_batch';
  
          fetchDiffFilesBatch({
            commit: () => {},
            state: {
              endpointBatch: `${endpointBatch}?view=${otherView}`,
              diffViewType: viewStyle,
            },
          })
            .then(() => {
              expect(mock.history.get[0].url).toContain(`view=${viewStyle}`);
              expect(mock.history.get[0].url).not.toContain(`view=${otherView}`);
            })
            .catch(() => {});
        },
      );
    });
  
    describe('fetchDiffFilesMeta', () => {
      const endpointMetadata = '/fetch/diffs_metadata.json?view=inline';
      const noFilesData = { ...diffMetadata };
      let mock;
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
  
        delete noFilesData.diff_files;
  
        mock.onGet(endpointMetadata).reply(200, diffMetadata);
      });
  
      it('should fetch diff meta information', (done) => {
        testAction(
          fetchDiffFilesMeta,
          {},
          { endpointMetadata, diffViewType: 'inline' },
          [
            { type: types.SET_LOADING, payload: true },
            { type: types.SET_LOADING, payload: false },
            { type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs },
            { type: types.SET_DIFF_METADATA, payload: noFilesData },
            // Workers are synchronous in Jest environment (see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58805)
            {
              type: types.SET_TREE_DATA,
              payload: treeWorkerUtils.generateTreeList(diffMetadata.diff_files),
            },
          ],
          [],
          () => {
            mock.restore();
            done();
          },
        );
      });
    });
  
    describe('fetchCoverageFiles', () => {
      let mock;
      const endpointCoverage = '/fetch';
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
      });
  
      afterEach(() => mock.restore());
  
      it('should commit SET_COVERAGE_DATA with received response', (done) => {
        const data = { files: { 'app.js': { 1: 0, 2: 1 } } };
  
        mock.onGet(endpointCoverage).reply(200, { data });
  
        testAction(
          fetchCoverageFiles,
          {},
          { endpointCoverage },
          [{ type: types.SET_COVERAGE_DATA, payload: { data } }],
          [],
          done,
        );
      });
  
      it('should show flash on API error', (done) => {
        mock.onGet(endpointCoverage).reply(400);
  
        testAction(fetchCoverageFiles, {}, { endpointCoverage }, [], [], () => {
          expect(createFlash).toHaveBeenCalledTimes(1);
          expect(createFlash).toHaveBeenCalledWith({
            message: expect.stringMatching('Something went wrong'),
          });
          done();
        });
      });
    });
  
    describe('setHighlightedRow', () => {
      it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
        testAction(setHighlightedRow, 'ABC_123', {}, [
          { type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' },
          { type: types.VIEW_DIFF_FILE, payload: 'ABC' },
        ]);
      });
    });
  
    describe('assignDiscussionsToDiff', () => {
      afterEach(() => {
        window.location.hash = '';
      });
  
      it('should merge discussions into diffs', (done) => {
        window.location.hash = 'ABC_123';
  
        const state = {
          diffFiles: [
            {
              file_hash: 'ABC',
              parallel_diff_lines: [
                {
                  left: {
                    line_code: 'ABC_1_1',
                    discussions: [],
                  },
                  right: {
                    line_code: 'ABC_1_1',
                    discussions: [],
                  },
                },
              ],
              highlighted_diff_lines: [
                {
                  line_code: 'ABC_1_1',
                  discussions: [],
                  old_line: 5,
                  new_line: null,
                },
              ],
              diff_refs: {
                base_sha: 'abc',
                head_sha: 'def',
                start_sha: 'ghi',
              },
              new_path: 'file1',
              old_path: 'file2',
            },
          ],
        };
  
        const diffPosition = {
          base_sha: 'abc',
          head_sha: 'def',
          start_sha: 'ghi',
          new_line: null,
          new_path: 'file1',
          old_line: 5,
          old_path: 'file2',
        };
  
        const singleDiscussion = {
          line_code: 'ABC_1_1',
          diff_discussion: {},
          diff_file: {
            file_hash: 'ABC',
          },
          file_hash: 'ABC',
          resolvable: true,
          position: diffPosition,
          original_position: diffPosition,
        };
  
        const discussions = [singleDiscussion];
  
        testAction(
          assignDiscussionsToDiff,
          discussions,
          state,
          [
            {
              type: types.SET_LINE_DISCUSSIONS_FOR_FILE,
              payload: {
                discussion: singleDiscussion,
                diffPositionByLineCode: {
                  ABC_1_1: {
                    base_sha: 'abc',
                    head_sha: 'def',
                    start_sha: 'ghi',
                    new_line: null,
                    new_path: 'file1',
                    old_line: 5,
                    old_path: 'file2',
                    line_range: null,
                    line_code: 'ABC_1_1',
                    position_type: 'text',
                  },
                },
                hash: 'ABC_123',
              },
            },
          ],
          [],
          done,
        );
      });
  
      it('dispatches setCurrentDiffFileIdFromNote with note ID', (done) => {
        window.location.hash = 'note_123';
  
        testAction(
          assignDiscussionsToDiff,
          [],
          { diffFiles: [] },
          [],
          [{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
          done,
        );
      });
    });
  
    describe('removeDiscussionsFromDiff', () => {
      it('should remove discussions from diffs', (done) => {
        const state = {
          diffFiles: [
            {
              file_hash: 'ABC',
              parallel_diff_lines: [
                {
                  left: {
                    line_code: 'ABC_1_1',
                    discussions: [
                      {
                        id: 1,
                      },
                    ],
                  },
                  right: {
                    line_code: 'ABC_1_1',
                    discussions: [],
                  },
                },
              ],
              highlighted_diff_lines: [
                {
                  line_code: 'ABC_1_1',
                  discussions: [],
                },
              ],
            },
          ],
        };
        const singleDiscussion = {
          id: '1',
          file_hash: 'ABC',
          line_code: 'ABC_1_1',
        };
  
        testAction(
          removeDiscussionsFromDiff,
          singleDiscussion,
          state,
          [
            {
              type: types.REMOVE_LINE_DISCUSSIONS_FOR_FILE,
              payload: {
                id: '1',
                fileHash: 'ABC',
                lineCode: 'ABC_1_1',
              },
            },
          ],
          [],
          done,
        );
      });
    });
  
    describe('startRenderDiffsQueue', () => {
      it('should set all files to RENDER_FILE', () => {
        const state = {
          diffFiles: [
            {
              id: 1,
              renderIt: false,
              viewer: {
                automaticallyCollapsed: false,
              },
            },
            {
              id: 2,
              renderIt: false,
              viewer: {
                automaticallyCollapsed: false,
              },
            },
          ],
        };
  
        const pseudoCommit = (commitType, file) => {
          expect(commitType).toBe(types.RENDER_FILE);
          Object.assign(file, {
            renderIt: true,
          });
        };
  
        startRenderDiffsQueue({ state, commit: pseudoCommit });
  
        expect(state.diffFiles[0].renderIt).toBe(true);
        expect(state.diffFiles[1].renderIt).toBe(true);
      });
    });
  
    describe('setInlineDiffViewType', () => {
      it('should set diff view type to inline and also set the cookie properly', (done) => {
        testAction(
          setInlineDiffViewType,
          null,
          {},
          [{ type: types.SET_DIFF_VIEW_TYPE, payload: INLINE_DIFF_VIEW_TYPE }],
          [],
          () => {
            setImmediate(() => {
              expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE);
              done();
            });
          },
        );
      });
    });
  
    describe('setParallelDiffViewType', () => {
      it('should set diff view type to parallel and also set the cookie properly', (done) => {
        testAction(
          setParallelDiffViewType,
          null,
          {},
          [{ type: types.SET_DIFF_VIEW_TYPE, payload: PARALLEL_DIFF_VIEW_TYPE }],
          [],
          () => {
            setImmediate(() => {
              expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE);
              done();
            });
          },
        );
      });
    });
  
    describe('showCommentForm', () => {
      it('should call mutation to show comment form', (done) => {
        const payload = { lineCode: 'lineCode', fileHash: 'hash' };
  
        testAction(
          showCommentForm,
          payload,
          {},
          [{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: true } }],
          [],
          done,
        );
      });
    });
  
    describe('cancelCommentForm', () => {
      it('should call mutation to cancel comment form', (done) => {
        const payload = { lineCode: 'lineCode', fileHash: 'hash' };
  
        testAction(
          cancelCommentForm,
          payload,
          {},
          [{ type: types.TOGGLE_LINE_HAS_FORM, payload: { ...payload, hasForm: false } }],
          [],
          done,
        );
      });
    });
  
    describe('loadMoreLines', () => {
      it('should call mutation to show comment form', (done) => {
        const endpoint = '/diffs/load/more/lines';
        const params = { since: 6, to: 26 };
        const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
        const fileHash = 'ff9200';
        const isExpandDown = false;
        const nextLineNumbers = {};
        const options = { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers };
        const mock = new MockAdapter(axios);
        const contextLines = { contextLines: [{ lineCode: 6 }] };
        mock.onGet(endpoint).reply(200, contextLines);
  
        testAction(
          loadMoreLines,
          options,
          {},
          [
            {
              type: types.ADD_CONTEXT_LINES,
              payload: { lineNumbers, contextLines, params, fileHash, isExpandDown, nextLineNumbers },
            },
          ],
          [],
          () => {
            mock.restore();
            done();
          },
        );
      });
    });
  
    describe('loadCollapsedDiff', () => {
      const state = { showWhitespace: true };
      it('should fetch data and call mutation with response and the give parameter', (done) => {
        const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' };
        const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
        const mock = new MockAdapter(axios);
        const commit = jest.fn();
        mock.onGet(file.loadCollapsedDiffUrl).reply(200, data);
  
        loadCollapsedDiff({ commit, getters: { commitId: null }, state }, file)
          .then(() => {
            expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data });
  
            mock.restore();
            done();
          })
          .catch(done.fail);
      });
  
      it('should fetch data without commit ID', () => {
        const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
        const getters = {
          commitId: null,
        };
  
        jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
  
        loadCollapsedDiff({ commit() {}, getters, state }, file);
  
        expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
          params: { commit_id: null, w: '0' },
        });
      });
  
      it('should fetch data with commit ID', () => {
        const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
        const getters = {
          commitId: '123',
        };
  
        jest.spyOn(axios, 'get').mockReturnValue(Promise.resolve({ data: {} }));
  
        loadCollapsedDiff({ commit() {}, getters, state }, file);
  
        expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
          params: { commit_id: '123', w: '0' },
        });
      });
    });
  
    describe('toggleFileDiscussions', () => {
      it('should dispatch collapseDiscussion when all discussions are expanded', () => {
        const getters = {
          getDiffFileDiscussions: jest.fn(() => [{ id: 1 }]),
          diffHasAllExpandedDiscussions: jest.fn(() => true),
          diffHasAllCollapsedDiscussions: jest.fn(() => false),
        };
  
        const dispatch = jest.fn();
  
        toggleFileDiscussions({ getters, dispatch });
  
        expect(dispatch).toHaveBeenCalledWith(
          'collapseDiscussion',
          { discussionId: 1 },
          { root: true },
        );
      });
  
      it('should dispatch expandDiscussion when all discussions are collapsed', () => {
        const getters = {
          getDiffFileDiscussions: jest.fn(() => [{ id: 1 }]),
          diffHasAllExpandedDiscussions: jest.fn(() => false),
          diffHasAllCollapsedDiscussions: jest.fn(() => true),
        };
  
        const dispatch = jest.fn();
  
        toggleFileDiscussions({ getters, dispatch });
  
        expect(dispatch).toHaveBeenCalledWith(
          'expandDiscussion',
          { discussionId: 1 },
          { root: true },
        );
      });
  
      it('should dispatch expandDiscussion when some discussions are collapsed and others are expanded for the collapsed discussion', () => {
        const getters = {
          getDiffFileDiscussions: jest.fn(() => [{ expanded: false, id: 1 }]),
          diffHasAllExpandedDiscussions: jest.fn(() => false),
          diffHasAllCollapsedDiscussions: jest.fn(() => false),
        };
  
        const dispatch = jest.fn();
  
        toggleFileDiscussions({ getters, dispatch });
  
        expect(dispatch).toHaveBeenCalledWith(
          'expandDiscussion',
          { discussionId: 1 },
          { root: true },
        );
      });
    });
  
    describe('scrollToLineIfNeededInline', () => {
      const lineMock = {
        line_code: 'ABC_123',
      };
  
      it('should not call handleLocationHash when there is not hash', () => {
        window.location.hash = '';
  
        scrollToLineIfNeededInline({}, lineMock);
  
        expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
      });
  
      it('should not call handleLocationHash when the hash does not match any line', () => {
        window.location.hash = 'XYZ_456';
  
        scrollToLineIfNeededInline({}, lineMock);
  
        expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
      });
  
      it('should call handleLocationHash only when the hash matches a line', () => {
        window.location.hash = 'ABC_123';
  
        scrollToLineIfNeededInline(
          {},
          {
            lineCode: 'ABC_456',
          },
        );
        scrollToLineIfNeededInline({}, lineMock);
        scrollToLineIfNeededInline(
          {},
          {
            lineCode: 'XYZ_456',
          },
        );
  
        expect(commonUtils.handleLocationHash).toHaveBeenCalled();
        expect(commonUtils.handleLocationHash).toHaveBeenCalledTimes(1);
      });
    });
  
    describe('scrollToLineIfNeededParallel', () => {
      const lineMock = {
        left: null,
        right: {
          line_code: 'ABC_123',
        },
      };
  
      it('should not call handleLocationHash when there is not hash', () => {
        window.location.hash = '';
  
        scrollToLineIfNeededParallel({}, lineMock);
  
        expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
      });
  
      it('should not call handleLocationHash when the hash does not match any line', () => {
        window.location.hash = 'XYZ_456';
  
        scrollToLineIfNeededParallel({}, lineMock);
  
        expect(commonUtils.handleLocationHash).not.toHaveBeenCalled();
      });
  
      it('should call handleLocationHash only when the hash matches a line', () => {
        window.location.hash = 'ABC_123';
  
        scrollToLineIfNeededParallel(
          {},
          {
            left: null,
            right: {
              lineCode: 'ABC_456',
            },
          },
        );
        scrollToLineIfNeededParallel({}, lineMock);
        scrollToLineIfNeededParallel(
          {},
          {
            left: null,
            right: {
              lineCode: 'XYZ_456',
            },
          },
        );
  
        expect(commonUtils.handleLocationHash).toHaveBeenCalled();
        expect(commonUtils.handleLocationHash).toHaveBeenCalledTimes(1);
      });
    });
  
    describe('saveDiffDiscussion', () => {
      it('dispatches actions', (done) => {
        const commitId = 'something';
        const formData = {
          diffFile: { ...mockDiffFile },
          noteableData: {},
        };
        const note = {};
        const state = {
          commit: {
            id: commitId,
          },
        };
        const dispatch = jest.fn((name) => {
          switch (name) {
            case 'saveNote':
              return Promise.resolve({
                discussion: 'test',
              });
            case 'updateDiscussion':
              return Promise.resolve('discussion');
            default:
              return Promise.resolve({});
          }
        });
  
        saveDiffDiscussion({ state, dispatch }, { note, formData })
          .then(() => {
            expect(dispatch).toHaveBeenCalledTimes(5);
            expect(dispatch).toHaveBeenNthCalledWith(1, 'saveNote', expect.any(Object), {
              root: true,
            });
  
            const postData = dispatch.mock.calls[0][1];
            expect(postData.data.note.commit_id).toBe(commitId);
  
            expect(dispatch).toHaveBeenNthCalledWith(2, 'updateDiscussion', 'test', { root: true });
            expect(dispatch).toHaveBeenNthCalledWith(3, 'assignDiscussionsToDiff', ['discussion']);
          })
          .then(done)
          .catch(done.fail);
      });
    });
  
    describe('toggleTreeOpen', () => {
      it('commits TOGGLE_FOLDER_OPEN', (done) => {
        testAction(
          toggleTreeOpen,
          'path',
          {},
          [{ type: types.TOGGLE_FOLDER_OPEN, payload: 'path' }],
          [],
          done,
        );
      });
    });
  
    describe('scrollToFile', () => {
      let commit;
      const getters = { isVirtualScrollingEnabled: false };
  
      beforeEach(() => {
        commit = jest.fn();
      });
  
      it('updates location hash', () => {
        const state = {
          treeEntries: {
            path: {
              fileHash: 'test',
            },
          },
        };
  
        scrollToFile({ state, commit, getters }, { path: 'path' });
  
        expect(document.location.hash).toBe('#test');
      });
  
      it('commits VIEW_DIFF_FILE', () => {
        const state = {
          treeEntries: {
            path: {
              fileHash: 'test',
            },
          },
        };
  
        scrollToFile({ state, commit, getters }, { path: 'path' });
  
        expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, 'test');
      });
    });
  
    describe('setShowTreeList', () => {
      it('commits toggle', (done) => {
        testAction(
          setShowTreeList,
          { showTreeList: true },
          {},
          [{ type: types.SET_SHOW_TREE_LIST, payload: true }],
          [],
          done,
        );
      });
  
      it('updates localStorage', () => {
        jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
  
        setShowTreeList({ commit() {} }, { showTreeList: true });
  
        expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
      });
  
      it('does not update localStorage', () => {
        jest.spyOn(localStorage, 'setItem').mockImplementation(() => {});
  
        setShowTreeList({ commit() {} }, { showTreeList: true, saving: false });
  
        expect(localStorage.setItem).not.toHaveBeenCalled();
      });
    });
  
    describe('renderFileForDiscussionId', () => {
      const rootState = {
        notes: {
          discussions: [
            {
              id: '123',
              diff_file: {
                file_hash: 'HASH',
              },
            },
            {
              id: '456',
              diff_file: {
                file_hash: 'HASH',
              },
            },
          ],
        },
      };
      let commit;
      let $emit;
      const state = ({ collapsed, renderIt }) => ({
        diffFiles: [
          {
            file_hash: 'HASH',
            viewer: {
              automaticallyCollapsed: collapsed,
            },
            renderIt,
          },
        ],
      });
  
      beforeEach(() => {
        commit = jest.fn();
        $emit = jest.spyOn(eventHub, '$emit');
      });
  
      it('renders and expands file for the given discussion id', () => {
        const localState = state({ collapsed: true, renderIt: false });
  
        renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
  
        expect(commit).toHaveBeenCalledWith('RENDER_FILE', localState.diffFiles[0]);
        expect($emit).toHaveBeenCalledTimes(1);
        expect(commonUtils.scrollToElement).toHaveBeenCalledTimes(1);
      });
  
      it('jumps to discussion on already rendered and expanded file', () => {
        const localState = state({ collapsed: false, renderIt: true });
  
        renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
  
        expect(commit).not.toHaveBeenCalled();
        expect($emit).toHaveBeenCalledTimes(1);
        expect(commonUtils.scrollToElement).not.toHaveBeenCalled();
      });
    });
  
    describe('setRenderTreeList', () => {
      it('commits SET_RENDER_TREE_LIST', (done) => {
        testAction(
          setRenderTreeList,
          { renderTreeList: true },
          {},
          [{ type: types.SET_RENDER_TREE_LIST, payload: true }],
          [],
          done,
        );
      });
  
      it('sets localStorage', () => {
        setRenderTreeList({ commit() {} }, { renderTreeList: true });
  
        expect(localStorage.setItem).toHaveBeenCalledWith('mr_diff_tree_list', true);
      });
    });
  
    describe('setShowWhitespace', () => {
      const endpointUpdateUser = 'user/prefs';
      let putSpy;
      let mock;
      let gon;
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
        putSpy = jest.spyOn(axios, 'put');
        gon = window.gon;
  
        mock.onPut(endpointUpdateUser).reply(200, {});
        jest.spyOn(eventHub, '$emit').mockImplementation();
      });
  
      afterEach(() => {
        mock.restore();
        window.gon = gon;
      });
  
      it('commits SET_SHOW_WHITESPACE', (done) => {
        testAction(
          setShowWhitespace,
          { showWhitespace: true, updateDatabase: false },
          {},
          [{ type: types.SET_SHOW_WHITESPACE, payload: true }],
          [],
          done,
        );
      });
  
      it('saves to the database when the user is logged in', async () => {
        window.gon = { current_user_id: 12345 };
  
        await setShowWhitespace(
          { state: { endpointUpdateUser }, commit() {} },
          { showWhitespace: true, updateDatabase: true },
        );
  
        expect(putSpy).toHaveBeenCalledWith(endpointUpdateUser, { show_whitespace_in_diffs: true });
      });
  
      it('does not try to save to the API if the user is not logged in', async () => {
        window.gon = {};
  
        await setShowWhitespace(
          { state: { endpointUpdateUser }, commit() {} },
          { showWhitespace: true, updateDatabase: true },
        );
  
        expect(putSpy).not.toHaveBeenCalled();
      });
  
      it('emits eventHub event', async () => {
        await setShowWhitespace(
          { state: {}, commit() {} },
          { showWhitespace: true, updateDatabase: false },
        );
  
        expect(eventHub.$emit).toHaveBeenCalledWith('refetchDiffData');
      });
    });
  
    describe('setRenderIt', () => {
      it('commits RENDER_FILE', (done) => {
        testAction(setRenderIt, 'file', {}, [{ type: types.RENDER_FILE, payload: 'file' }], [], done);
      });
    });
  
    describe('receiveFullDiffError', () => {
      it('updates state with the file that did not load', (done) => {
        testAction(
          receiveFullDiffError,
          'file',
          {},
          [{ type: types.RECEIVE_FULL_DIFF_ERROR, payload: 'file' }],
          [],
          done,
        );
      });
    });
  
    describe('fetchFullDiff', () => {
      let mock;
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
      });
  
      afterEach(() => {
        mock.restore();
      });
  
      describe('success', () => {
        beforeEach(() => {
          mock.onGet(`${TEST_HOST}/context`).replyOnce(200, ['test']);
        });
  
        it('commits the success and dispatches an action to expand the new lines', (done) => {
          const file = {
            context_lines_path: `${TEST_HOST}/context`,
            file_path: 'test',
            file_hash: 'test',
          };
          testAction(
            fetchFullDiff,
            file,
            null,
            [{ type: types.RECEIVE_FULL_DIFF_SUCCESS, payload: { filePath: 'test' } }],
            [{ type: 'setExpandedDiffLines', payload: { file, data: ['test'] } }],
            done,
          );
        });
      });
  
      describe('error', () => {
        beforeEach(() => {
          mock.onGet(`${TEST_HOST}/context`).replyOnce(500);
        });
  
        it('dispatches receiveFullDiffError', (done) => {
          testAction(
            fetchFullDiff,
            { context_lines_path: `${TEST_HOST}/context`, file_path: 'test', file_hash: 'test' },
            null,
            [],
            [{ type: 'receiveFullDiffError', payload: 'test' }],
            done,
          );
        });
      });
    });
  
    describe('toggleFullDiff', () => {
      let state;
  
      beforeEach(() => {
        state = {
          diffFiles: [{ file_path: 'test', isShowingFullFile: false }],
        };
      });
  
      it('dispatches fetchFullDiff when file is not expanded', (done) => {
        testAction(
          toggleFullDiff,
          'test',
          state,
          [{ type: types.REQUEST_FULL_DIFF, payload: 'test' }],
          [{ type: 'fetchFullDiff', payload: state.diffFiles[0] }],
          done,
        );
      });
    });
  
    describe('switchToFullDiffFromRenamedFile', () => {
      const SUCCESS_URL = 'fakehost/context.success';
      const testFilePath = 'testpath';
      const updatedViewerName = 'testviewer';
      const preparedLine = { prepared: 'in-a-test' };
      const testFile = {
        file_path: testFilePath,
        file_hash: 'testhash',
        alternate_viewer: { name: updatedViewerName },
      };
      const updatedViewer = {
        name: updatedViewerName,
        automaticallyCollapsed: false,
        manuallyCollapsed: false,
      };
      const testData = [{ rich_text: 'test' }, { rich_text: 'file2' }];
      let renamedFile;
      let mock;
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
        jest.spyOn(utils, 'prepareLineForRenamedFile').mockImplementation(() => preparedLine);
      });
  
      afterEach(() => {
        renamedFile = null;
        mock.restore();
      });
  
      describe('success', () => {
        beforeEach(() => {
          renamedFile = { ...testFile, context_lines_path: SUCCESS_URL };
          mock.onGet(SUCCESS_URL).replyOnce(200, testData);
        });
  
        it.each`
          diffViewType
          ${INLINE_DIFF_VIEW_TYPE}
          ${PARALLEL_DIFF_VIEW_TYPE}
        `(
          'performs the correct mutations and starts a render queue for view type $diffViewType',
          ({ diffViewType }) => {
            return testAction(
              switchToFullDiffFromRenamedFile,
              { diffFile: renamedFile },
              { diffViewType },
              [
                {
                  type: types.SET_DIFF_FILE_VIEWER,
                  payload: { filePath: testFilePath, viewer: updatedViewer },
                },
                {
                  type: types.SET_CURRENT_VIEW_DIFF_FILE_LINES,
                  payload: { filePath: testFilePath, lines: [preparedLine, preparedLine] },
                },
              ],
              [{ type: 'startRenderDiffsQueue' }],
            );
          },
        );
      });
    });
  
    describe('setFileUserCollapsed', () => {
      it('commits SET_FILE_COLLAPSED', (done) => {
        testAction(
          setFileCollapsedByUser,
          { filePath: 'test', collapsed: true },
          null,
          [
            {
              type: types.SET_FILE_COLLAPSED,
              payload: { filePath: 'test', collapsed: true, trigger: 'manual' },
            },
          ],
          [],
          done,
        );
      });
    });
  
    describe('setExpandedDiffLines', () => {
      beforeEach(() => {
        utils.idleCallback.mockImplementation((cb) => {
          cb({ timeRemaining: () => 50 });
        });
      });
  
      it('commits SET_CURRENT_VIEW_DIFF_FILE_LINES when lines less than MAX_RENDERING_DIFF_LINES', (done) => {
        utils.convertExpandLines.mockImplementation(() => ['test']);
  
        testAction(
          setExpandedDiffLines,
          { file: { file_path: 'path' }, data: [] },
          { diffViewType: 'inline' },
          [
            {
              type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
              payload: { filePath: 'path', lines: ['test'] },
            },
          ],
          [],
          done,
        );
      });
  
      it('commits ADD_CURRENT_VIEW_DIFF_FILE_LINES when lines more than MAX_RENDERING_DIFF_LINES', (done) => {
        const lines = new Array(501).fill().map((_, i) => `line-${i}`);
        utils.convertExpandLines.mockReturnValue(lines);
  
        testAction(
          setExpandedDiffLines,
          { file: { file_path: 'path' }, data: [] },
          { diffViewType: 'inline' },
          [
            {
              type: 'SET_CURRENT_VIEW_DIFF_FILE_LINES',
              payload: { filePath: 'path', lines: lines.slice(0, 200) },
            },
            { type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' },
            ...new Array(301).fill().map((_, i) => ({
              type: 'ADD_CURRENT_VIEW_DIFF_FILE_LINES',
              payload: { filePath: 'path', line: `line-${i + 200}` },
            })),
            { type: 'TOGGLE_DIFF_FILE_RENDERING_MORE', payload: 'path' },
          ],
          [],
          done,
        );
      });
    });
  
    describe('setSuggestPopoverDismissed', () => {
      it('commits SET_SHOW_SUGGEST_POPOVER', (done) => {
        const state = { dismissEndpoint: `${TEST_HOST}/-/user_callouts` };
        const mock = new MockAdapter(axios);
        mock.onPost(state.dismissEndpoint).reply(200, {});
  
        jest.spyOn(axios, 'post');
  
        testAction(
          setSuggestPopoverDismissed,
          null,
          state,
          [{ type: types.SET_SHOW_SUGGEST_POPOVER }],
          [],
          () => {
            expect(axios.post).toHaveBeenCalledWith(state.dismissEndpoint, {
              feature_name: 'suggest_popover_dismissed',
            });
  
            mock.restore();
            done();
          },
        );
      });
    });
  
    describe('changeCurrentCommit', () => {
      it('commits the new commit information and re-requests the diff metadata for the commit', () => {
        return testAction(
          changeCurrentCommit,
          { commitId: 'NEW' },
          {
            commit: {
              id: 'OLD',
            },
            endpoint: 'URL/OLD',
            endpointBatch: 'URL/OLD',
            endpointMetadata: 'URL/OLD',
          },
          [
            { type: types.SET_DIFF_FILES, payload: [] },
            {
              type: types.SET_BASE_CONFIG,
              payload: {
                commit: {
                  id: 'OLD', // Not a typo: the action fired next will overwrite all of the `commit` in state
                },
                endpoint: 'URL/NEW',
                endpointBatch: 'URL/NEW',
                endpointMetadata: 'URL/NEW',
              },
            },
          ],
          [{ type: 'fetchDiffFilesMeta' }],
        );
      });
  
      it.each`
        commitId     | commit           | msg
        ${undefined} | ${{ id: 'OLD' }} | ${'`commitId` is a required argument'}
        ${'NEW'}     | ${null}          | ${'`state` must already contain a valid `commit`'}
        ${undefined} | ${null}          | ${'`commitId` is a required argument'}
      `(
        'returns a rejected promise with the error message $msg given `{ "commitId": $commitId, "state.commit": $commit }`',
        ({ commitId, commit, msg }) => {
          const err = new Error(msg);
          const actionReturn = testAction(
            changeCurrentCommit,
            { commitId },
            {
              endpoint: 'URL/OLD',
              endpointBatch: 'URL/OLD',
              endpointMetadata: 'URL/OLD',
              commit,
            },
            [],
            [],
          );
  
          return expect(actionReturn).rejects.toStrictEqual(err);
        },
      );
    });
  
    describe('moveToNeighboringCommit', () => {
      it.each`
        direction     | expected         | currentCommit
        ${'next'}     | ${'NEXTSHA'}     | ${{ next_commit_id: 'NEXTSHA' }}
        ${'previous'} | ${'PREVIOUSSHA'} | ${{ prev_commit_id: 'PREVIOUSSHA' }}
      `(
        'for the direction "$direction", dispatches the action to move to the SHA "$expected"',
        ({ direction, expected, currentCommit }) => {
          return testAction(
            moveToNeighboringCommit,
            { direction },
            { commit: currentCommit },
            [],
            [{ type: 'changeCurrentCommit', payload: { commitId: expected } }],
          );
        },
      );
  
      it.each`
        direction     | diffsAreLoading | currentCommit
        ${'next'}     | ${false}        | ${{ prev_commit_id: 'PREVIOUSSHA' }}
        ${'next'}     | ${true}         | ${{ prev_commit_id: 'PREVIOUSSHA' }}
        ${'next'}     | ${false}        | ${undefined}
        ${'previous'} | ${false}        | ${{ next_commit_id: 'NEXTSHA' }}
        ${'previous'} | ${true}         | ${{ next_commit_id: 'NEXTSHA' }}
        ${'previous'} | ${false}        | ${undefined}
      `(
        'given `{ "isloading": $diffsAreLoading, "commit": $currentCommit }` in state, no actions are dispatched',
        ({ direction, diffsAreLoading, currentCommit }) => {
          return testAction(
            moveToNeighboringCommit,
            { direction },
            { commit: currentCommit, isLoading: diffsAreLoading },
            [],
            [],
          );
        },
      );
    });
  
    describe('setCurrentDiffFileIdFromNote', () => {
      it('commits VIEW_DIFF_FILE', () => {
        const commit = jest.fn();
        const state = { diffFiles: [{ file_hash: '123' }] };
        const rootGetters = {
          getDiscussion: () => ({ diff_file: { file_hash: '123' } }),
          notesById: { 1: { discussion_id: '2' } },
        };
  
        setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
  
        expect(commit).toHaveBeenCalledWith(types.VIEW_DIFF_FILE, '123');
      });
  
      it('does not commit VIEW_DIFF_FILE when discussion has no diff_file', () => {
        const commit = jest.fn();
        const state = { diffFiles: [{ file_hash: '123' }] };
        const rootGetters = {
          getDiscussion: () => ({ id: '1' }),
          notesById: { 1: { discussion_id: '2' } },
        };
  
        setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
  
        expect(commit).not.toHaveBeenCalled();
      });
  
      it('does not commit VIEW_DIFF_FILE when diff file does not exist', () => {
        const commit = jest.fn();
        const state = { diffFiles: [{ file_hash: '123' }] };
        const rootGetters = {
          getDiscussion: () => ({ diff_file: { file_hash: '124' } }),
          notesById: { 1: { discussion_id: '2' } },
        };
  
        setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
  
        expect(commit).not.toHaveBeenCalled();
      });
    });
  
    describe('navigateToDiffFileIndex', () => {
      it('commits VIEW_DIFF_FILE', (done) => {
        testAction(
          navigateToDiffFileIndex,
          0,
          { diffFiles: [{ file_hash: '123' }] },
          [{ type: types.VIEW_DIFF_FILE, payload: '123' }],
          [],
          done,
        );
      });
    });
  
    describe('setFileByFile', () => {
      const updateUserEndpoint = 'user/prefs';
      let putSpy;
      let mock;
  
      beforeEach(() => {
        mock = new MockAdapter(axios);
        putSpy = jest.spyOn(axios, 'put');
  
        mock.onPut(updateUserEndpoint).reply(200, {});
      });
  
      afterEach(() => {
        mock.restore();
      });
  
      it.each`
        value
        ${true}
        ${false}
      `(
        'commits SET_FILE_BY_FILE and persists the File-by-File user preference with the new value $value',
        async ({ value }) => {
          await testAction(
            setFileByFile,
            { fileByFile: value },
            {
              viewDiffsFileByFile: null,
              endpointUpdateUser: updateUserEndpoint,
            },
            [{ type: types.SET_FILE_BY_FILE, payload: value }],
            [],
          );
  
          expect(putSpy).toHaveBeenCalledWith(updateUserEndpoint, { view_diffs_file_by_file: value });
        },
      );
    });
  
    describe('reviewFile', () => {
      const file = {
        id: '123',
        file_identifier_hash: 'abc',
        load_collapsed_diff_url: 'gitlab-org/gitlab-test/-/merge_requests/1/diffs',
      };
      it.each`
        reviews             | diffFile | reviewed
        ${{ abc: ['123'] }} | ${file}  | ${true}
        ${{}}               | ${file}  | ${false}
      `(
        'sets reviews ($reviews) to localStorage and state for file $file if it is marked reviewed=$reviewed',
        ({ reviews, diffFile, reviewed }) => {
          const commitSpy = jest.fn();
          const getterSpy = jest.fn().mockReturnValue([]);
  
          reviewFile(
            {
              commit: commitSpy,
              getters: {
                fileReviews: getterSpy,
              },
              state: {
                mrReviews: { abc: ['123'] },
              },
            },
            {
              file: diffFile,
              reviewed,
            },
          );
  
          expect(localStorage.setItem).toHaveBeenCalledTimes(1);
          expect(localStorage.setItem).toHaveBeenCalledWith(
            'gitlab-org/gitlab-test/-/merge_requests/1-file-reviews',
            JSON.stringify(reviews),
          );
          expect(commitSpy).toHaveBeenCalledWith(types.SET_MR_FILE_REVIEWS, reviews);
        },
      );
    });
  });
  