addons/isl/src/__tests__/MergeConflicts.test.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import {act, fireEvent, render, screen, waitFor} from '@testing-library/react';
b69ab319import App from '../App';
b69ab3110import {mostRecentSubscriptionIds} from '../serverAPIState';
b69ab3111import {
b69ab3112 closeCommitInfoSidebar,
b69ab3113 COMMIT,
b69ab3114 expectMessageSentToServer,
b69ab3115 getLastMessageOfTypeSentToServer,
b69ab3116 resetTestMessages,
b69ab3117 simulateCommits,
b69ab3118 simulateMessageFromServer,
b69ab3119 simulateUncommittedChangedFiles,
b69ab3120} from '../testUtils';
b69ab3121import {ConflictType} from '../types';
b69ab3122
b69ab3123describe('CommitTreeList', () => {
b69ab3124 beforeEach(() => {
b69ab3125 resetTestMessages();
b69ab3126 render(<App />);
b69ab3127 act(() => {
b69ab3128 closeCommitInfoSidebar();
b69ab3129 simulateCommits({
b69ab3130 value: [
b69ab3131 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab3132 COMMIT('a', 'My Commit', '1'),
b69ab3133 COMMIT('b', 'Another Commit', 'a', {isDot: true}),
b69ab3134 ],
b69ab3135 });
b69ab3136 simulateUncommittedChangedFiles({
b69ab3137 value: [
b69ab3138 {path: 'src/file1.js', status: 'M'},
b69ab3139 {path: 'src/file2.js', status: 'M'},
b69ab3140 {path: 'src/file3.js', status: 'M'},
b69ab3141 ],
b69ab3142 });
b69ab3143 });
b69ab3144
b69ab3145 act(() => {
b69ab3146 expectMessageSentToServer({
b69ab3147 type: 'subscribe',
b69ab3148 kind: 'mergeConflicts',
b69ab3149 subscriptionID: expect.anything(),
b69ab3150 });
b69ab3151 simulateMessageFromServer({
b69ab3152 type: 'subscriptionResult',
b69ab3153 kind: 'mergeConflicts',
b69ab3154 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
b69ab3155 data: {
b69ab3156 state: 'loading',
b69ab3157 },
b69ab3158 });
b69ab3159 });
b69ab3160 });
b69ab3161
b69ab3162 it('renders merge conflicts spinner while loading', () => {
b69ab3163 expect(screen.getByTestId('merge-conflicts-spinner')).toBeInTheDocument();
b69ab3164 });
b69ab3165
b69ab3166 describe('after conflicts load', () => {
b69ab3167 beforeEach(() => {
b69ab3168 act(() => {
b69ab3169 simulateMessageFromServer({
b69ab3170 type: 'subscriptionResult',
b69ab3171 kind: 'mergeConflicts',
b69ab3172 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
b69ab3173 data: {
b69ab3174 state: 'loaded',
b69ab3175 command: 'rebase',
b69ab3176 toContinue: 'rebase --continue',
b69ab3177 toAbort: 'rebase --abort',
b69ab3178 files: [
b69ab3179 {path: 'src/file2.js', status: 'U', conflictType: ConflictType.BothChanged},
b69ab3180 {path: 'src/file3.js', status: 'Resolved', conflictType: ConflictType.BothChanged},
b69ab3181 ],
b69ab3182 fetchStartTimestamp: 1,
b69ab3183 fetchCompletedTimestamp: 2,
b69ab3184 },
b69ab3185 });
b69ab3186 });
b69ab3187 });
b69ab3188
b69ab3189 it('renders merge conflicts changes', () => {
b69ab3190 expect(screen.getByText('file2.js', {exact: false})).toBeInTheDocument();
b69ab3191 expect(screen.getByText('file3.js', {exact: false})).toBeInTheDocument();
b69ab3192
b69ab3193 // uncommitted changes are not there
b69ab3194 expect(screen.queryByText('file1.js', {exact: false})).not.toBeInTheDocument();
b69ab3195 });
b69ab3196
b69ab3197 it("doesn't allow continue until conflicts resolved", () => {
b69ab3198 expect(
b69ab3199 screen.queryByText('All Merge Conflicts Resolved', {exact: false}),
b69ab31100 ).not.toBeInTheDocument();
b69ab31101 expect(
b69ab31102 screen.queryByText('Resolve conflicts to continue rebase', {exact: false}),
b69ab31103 ).toBeInTheDocument();
b69ab31104
b69ab31105 expect(
b69ab31106 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
b69ab31107 ).toEqual(true);
b69ab31108
b69ab31109 act(() => {
b69ab31110 simulateMessageFromServer({
b69ab31111 type: 'subscriptionResult',
b69ab31112 kind: 'mergeConflicts',
b69ab31113 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
b69ab31114 data: {
b69ab31115 state: 'loaded',
b69ab31116 command: 'rebase',
b69ab31117 toContinue: 'rebase --continue',
b69ab31118 toAbort: 'rebase --abort',
b69ab31119 files: [
b69ab31120 {path: 'src/file2.js', status: 'Resolved', conflictType: ConflictType.BothChanged},
b69ab31121 {path: 'src/file3.js', status: 'Resolved', conflictType: ConflictType.BothChanged},
b69ab31122 ],
b69ab31123 fetchStartTimestamp: 1,
b69ab31124 fetchCompletedTimestamp: 2,
b69ab31125 },
b69ab31126 });
b69ab31127 });
b69ab31128
b69ab31129 expect(
b69ab31130 screen.queryByText('All Merge Conflicts Resolved', {exact: false}),
b69ab31131 ).toBeInTheDocument();
b69ab31132 expect(
b69ab31133 screen.queryByText('Resolve conflicts to continue rebase', {exact: false}),
b69ab31134 ).not.toBeInTheDocument();
b69ab31135
b69ab31136 expect((screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled).toBe(
b69ab31137 false,
b69ab31138 );
b69ab31139 });
b69ab31140
b69ab31141 it('uses optimistic state to render resolved files', () => {
b69ab31142 const resolveButton = screen.getByTestId('file-action-resolve');
b69ab31143 act(() => {
b69ab31144 fireEvent.click(resolveButton);
b69ab31145 });
b69ab31146 // resolve button is no longer there
b69ab31147 expect(screen.queryByTestId('file-action-resolve')).not.toBeInTheDocument();
b69ab31148 });
b69ab31149
b69ab31150 it('lets you continue when conflicts are optimistically resolved', () => {
b69ab31151 const resolveButton = screen.getByTestId('file-action-resolve');
b69ab31152 act(() => {
b69ab31153 fireEvent.click(resolveButton);
b69ab31154 });
b69ab31155
b69ab31156 // continue is no longer disabled
b69ab31157 expect(
b69ab31158 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
b69ab31159 ).toEqual(false);
b69ab31160 });
b69ab31161
b69ab31162 it('disables continue button while running', async () => {
b69ab31163 const resolveButton = screen.getByTestId('file-action-resolve');
b69ab31164 act(() => {
b69ab31165 fireEvent.click(resolveButton);
b69ab31166 });
b69ab31167 const continueButton = screen.getByTestId('conflict-continue-button');
b69ab31168 act(() => {
b69ab31169 fireEvent.click(continueButton);
b69ab31170 });
b69ab31171
b69ab31172 await waitFor(() => {
b69ab31173 expectMessageSentToServer({
b69ab31174 type: 'runOperation',
b69ab31175 operation: expect.objectContaining({args: ['continue']}),
b69ab31176 });
b69ab31177 });
b69ab31178
b69ab31179 const message = getLastMessageOfTypeSentToServer('runOperation');
b69ab31180
b69ab31181 act(() => {
b69ab31182 simulateMessageFromServer({
b69ab31183 type: 'operationProgress',
b69ab31184 id: message!.operation.id,
b69ab31185 kind: 'spawn',
b69ab31186 queue: [],
b69ab31187 });
b69ab31188 });
b69ab31189
b69ab31190 expect(
b69ab31191 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
b69ab31192 ).toEqual(true);
b69ab31193
b69ab31194 // simulate continue finishing
b69ab31195 act(() => {
b69ab31196 simulateMessageFromServer({
b69ab31197 type: 'operationProgress',
b69ab31198 kind: 'exit',
b69ab31199 exitCode: 0,
b69ab31200 id: 'foo',
b69ab31201 timestamp: 1234,
b69ab31202 });
b69ab31203 });
b69ab31204
b69ab31205 // We will soon get the next set of merge conflicts as null.
b69ab31206 // In the mean time, after `continue` has run, we still disable the button.
b69ab31207 expect(
b69ab31208 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
b69ab31209 ).toEqual(true);
b69ab31210
b69ab31211 act(() => {
b69ab31212 simulateMessageFromServer({
b69ab31213 type: 'subscriptionResult',
b69ab31214 kind: 'mergeConflicts',
b69ab31215 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
b69ab31216 data: undefined,
b69ab31217 });
b69ab31218 });
b69ab31219 expect(screen.queryByTestId('conflict-continue-button')).not.toBeInTheDocument();
b69ab31220 });
b69ab31221 });
b69ab31222});