6.8 KB223 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import {act, fireEvent, render, screen, waitFor} from '@testing-library/react';
9import App from '../App';
10import {mostRecentSubscriptionIds} from '../serverAPIState';
11import {
12 closeCommitInfoSidebar,
13 COMMIT,
14 expectMessageSentToServer,
15 getLastMessageOfTypeSentToServer,
16 resetTestMessages,
17 simulateCommits,
18 simulateMessageFromServer,
19 simulateUncommittedChangedFiles,
20} from '../testUtils';
21import {ConflictType} from '../types';
22
23describe('CommitTreeList', () => {
24 beforeEach(() => {
25 resetTestMessages();
26 render(<App />);
27 act(() => {
28 closeCommitInfoSidebar();
29 simulateCommits({
30 value: [
31 COMMIT('1', 'some public base', '0', {phase: 'public'}),
32 COMMIT('a', 'My Commit', '1'),
33 COMMIT('b', 'Another Commit', 'a', {isDot: true}),
34 ],
35 });
36 simulateUncommittedChangedFiles({
37 value: [
38 {path: 'src/file1.js', status: 'M'},
39 {path: 'src/file2.js', status: 'M'},
40 {path: 'src/file3.js', status: 'M'},
41 ],
42 });
43 });
44
45 act(() => {
46 expectMessageSentToServer({
47 type: 'subscribe',
48 kind: 'mergeConflicts',
49 subscriptionID: expect.anything(),
50 });
51 simulateMessageFromServer({
52 type: 'subscriptionResult',
53 kind: 'mergeConflicts',
54 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
55 data: {
56 state: 'loading',
57 },
58 });
59 });
60 });
61
62 it('renders merge conflicts spinner while loading', () => {
63 expect(screen.getByTestId('merge-conflicts-spinner')).toBeInTheDocument();
64 });
65
66 describe('after conflicts load', () => {
67 beforeEach(() => {
68 act(() => {
69 simulateMessageFromServer({
70 type: 'subscriptionResult',
71 kind: 'mergeConflicts',
72 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
73 data: {
74 state: 'loaded',
75 command: 'rebase',
76 toContinue: 'rebase --continue',
77 toAbort: 'rebase --abort',
78 files: [
79 {path: 'src/file2.js', status: 'U', conflictType: ConflictType.BothChanged},
80 {path: 'src/file3.js', status: 'Resolved', conflictType: ConflictType.BothChanged},
81 ],
82 fetchStartTimestamp: 1,
83 fetchCompletedTimestamp: 2,
84 },
85 });
86 });
87 });
88
89 it('renders merge conflicts changes', () => {
90 expect(screen.getByText('file2.js', {exact: false})).toBeInTheDocument();
91 expect(screen.getByText('file3.js', {exact: false})).toBeInTheDocument();
92
93 // uncommitted changes are not there
94 expect(screen.queryByText('file1.js', {exact: false})).not.toBeInTheDocument();
95 });
96
97 it("doesn't allow continue until conflicts resolved", () => {
98 expect(
99 screen.queryByText('All Merge Conflicts Resolved', {exact: false}),
100 ).not.toBeInTheDocument();
101 expect(
102 screen.queryByText('Resolve conflicts to continue rebase', {exact: false}),
103 ).toBeInTheDocument();
104
105 expect(
106 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
107 ).toEqual(true);
108
109 act(() => {
110 simulateMessageFromServer({
111 type: 'subscriptionResult',
112 kind: 'mergeConflicts',
113 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
114 data: {
115 state: 'loaded',
116 command: 'rebase',
117 toContinue: 'rebase --continue',
118 toAbort: 'rebase --abort',
119 files: [
120 {path: 'src/file2.js', status: 'Resolved', conflictType: ConflictType.BothChanged},
121 {path: 'src/file3.js', status: 'Resolved', conflictType: ConflictType.BothChanged},
122 ],
123 fetchStartTimestamp: 1,
124 fetchCompletedTimestamp: 2,
125 },
126 });
127 });
128
129 expect(
130 screen.queryByText('All Merge Conflicts Resolved', {exact: false}),
131 ).toBeInTheDocument();
132 expect(
133 screen.queryByText('Resolve conflicts to continue rebase', {exact: false}),
134 ).not.toBeInTheDocument();
135
136 expect((screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled).toBe(
137 false,
138 );
139 });
140
141 it('uses optimistic state to render resolved files', () => {
142 const resolveButton = screen.getByTestId('file-action-resolve');
143 act(() => {
144 fireEvent.click(resolveButton);
145 });
146 // resolve button is no longer there
147 expect(screen.queryByTestId('file-action-resolve')).not.toBeInTheDocument();
148 });
149
150 it('lets you continue when conflicts are optimistically resolved', () => {
151 const resolveButton = screen.getByTestId('file-action-resolve');
152 act(() => {
153 fireEvent.click(resolveButton);
154 });
155
156 // continue is no longer disabled
157 expect(
158 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
159 ).toEqual(false);
160 });
161
162 it('disables continue button while running', async () => {
163 const resolveButton = screen.getByTestId('file-action-resolve');
164 act(() => {
165 fireEvent.click(resolveButton);
166 });
167 const continueButton = screen.getByTestId('conflict-continue-button');
168 act(() => {
169 fireEvent.click(continueButton);
170 });
171
172 await waitFor(() => {
173 expectMessageSentToServer({
174 type: 'runOperation',
175 operation: expect.objectContaining({args: ['continue']}),
176 });
177 });
178
179 const message = getLastMessageOfTypeSentToServer('runOperation');
180
181 act(() => {
182 simulateMessageFromServer({
183 type: 'operationProgress',
184 id: message!.operation.id,
185 kind: 'spawn',
186 queue: [],
187 });
188 });
189
190 expect(
191 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
192 ).toEqual(true);
193
194 // simulate continue finishing
195 act(() => {
196 simulateMessageFromServer({
197 type: 'operationProgress',
198 kind: 'exit',
199 exitCode: 0,
200 id: 'foo',
201 timestamp: 1234,
202 });
203 });
204
205 // We will soon get the next set of merge conflicts as null.
206 // In the mean time, after `continue` has run, we still disable the button.
207 expect(
208 (screen.queryByTestId('conflict-continue-button') as HTMLButtonElement).disabled,
209 ).toEqual(true);
210
211 act(() => {
212 simulateMessageFromServer({
213 type: 'subscriptionResult',
214 kind: 'mergeConflicts',
215 subscriptionID: mostRecentSubscriptionIds.mergeConflicts,
216 data: undefined,
217 });
218 });
219 expect(screen.queryByTestId('conflict-continue-button')).not.toBeInTheDocument();
220 });
221 });
222});
223