8.3 KB282 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, within} from '@testing-library/react';
9import userEvent from '@testing-library/user-event';
10import * as utils from 'shared/utils';
11import App from '../../App';
12import {CommitInfoTestUtils} from '../../testQueries';
13import {
14 COMMIT,
15 expectMessageSentToServer,
16 getLastMessageOfTypeSentToServer,
17 openCommitInfoSidebar,
18 resetTestMessages,
19 simulateCommits,
20 simulateMessageFromServer,
21 simulateUncommittedChangedFiles,
22} from '../../testUtils';
23import {CommandRunner} from '../../types';
24
25describe('CommitOperation', () => {
26 beforeEach(() => {
27 resetTestMessages();
28 render(<App />);
29 act(() => {
30 expectMessageSentToServer({
31 type: 'subscribe',
32 kind: 'smartlogCommits',
33 subscriptionID: expect.anything(),
34 });
35 simulateUncommittedChangedFiles({
36 value: [
37 {path: 'file1.txt', status: 'M'},
38 {path: 'file2.txt', status: 'A'},
39 {path: 'file3.txt', status: 'R'},
40 ],
41 });
42 simulateCommits({
43 value: [
44 COMMIT('2', 'master', '00', {phase: 'public', remoteBookmarks: ['remote/master']}),
45 COMMIT('1', 'Commit 1', '0', {phase: 'public'}),
46 COMMIT('a', 'Commit A', '1'),
47 COMMIT('b', 'Commit B', 'a', {isDot: true}),
48 ],
49 });
50 });
51 });
52
53 const clickQuickCommit = async () => {
54 const quickCommitButton = screen.getByTestId('quick-commit-button');
55 act(() => {
56 fireEvent.click(quickCommitButton);
57 });
58 await waitFor(() =>
59 expectMessageSentToServer({
60 type: 'runOperation',
61 operation: expect.objectContaining({
62 args: expect.arrayContaining(['commit']),
63 }),
64 }),
65 );
66 };
67
68 const clickCheckboxForFile = (inside: HTMLElement, fileName: string) => {
69 act(() => {
70 const checkbox = within(within(inside).getByTestId(`changed-file-${fileName}`)).getByTestId(
71 'file-selection-checkbox',
72 );
73 expect(checkbox).toBeInTheDocument();
74 fireEvent.click(checkbox);
75 });
76 };
77
78 it('runs commit', async () => {
79 await clickQuickCommit();
80
81 expectMessageSentToServer({
82 type: 'runOperation',
83 operation: {
84 args: [
85 'commit',
86 '--addremove',
87 '--message',
88 expect.stringContaining(`Temporary Commit at`),
89 ],
90 id: expect.anything(),
91 runner: CommandRunner.Sapling,
92 trackEventName: 'CommitOperation',
93 },
94 });
95 });
96
97 it('runs commit with subset of files selected', async () => {
98 const commitTree = screen.getByTestId('commit-tree-root');
99 clickCheckboxForFile(commitTree, 'file2.txt');
100
101 await clickQuickCommit();
102
103 expectMessageSentToServer({
104 type: 'runOperation',
105 operation: {
106 args: [
107 'commit',
108 '--addremove',
109 '--message',
110 expect.stringContaining(`Temporary Commit at`),
111 {type: 'repo-relative-file', path: 'file1.txt'},
112 {type: 'repo-relative-file', path: 'file3.txt'},
113 ],
114 id: expect.anything(),
115 runner: CommandRunner.Sapling,
116 trackEventName: 'CommitFileSubsetOperation',
117 },
118 });
119 });
120
121 it('changed files are shown in commit info view', async () => {
122 const commitTree = screen.getByTestId('commit-tree-root');
123 clickCheckboxForFile(commitTree, 'file2.txt');
124
125 const quickInput = screen.getByTestId('quick-commit-title');
126
127 act(() => {
128 userEvent.type(quickInput, 'My Commit');
129 });
130
131 await clickQuickCommit();
132
133 expect(
134 within(screen.getByTestId('changes-to-amend')).queryByText(/file1.txt/),
135 ).not.toBeInTheDocument();
136 expect(
137 within(screen.getByTestId('changes-to-amend')).getByText(/file2.txt/),
138 ).toBeInTheDocument();
139 expect(
140 within(screen.getByTestId('changes-to-amend')).queryByText(/file3.txt/),
141 ).not.toBeInTheDocument();
142
143 expect(
144 within(screen.getByTestId('committed-changes')).getByText(/file1.txt/),
145 ).toBeInTheDocument();
146 expect(
147 within(screen.getByTestId('committed-changes')).queryByText(/file2.txt/),
148 ).not.toBeInTheDocument();
149 expect(
150 within(screen.getByTestId('committed-changes')).getByText(/file3.txt/),
151 ).toBeInTheDocument();
152 });
153
154 it('uses commit template if provided', async () => {
155 await waitFor(() => {
156 expectMessageSentToServer({type: 'fetchCommitMessageTemplate'});
157 });
158 act(() => {
159 simulateMessageFromServer({
160 type: 'fetchedCommitMessageTemplate',
161 template: 'Template Title\n\nSummary: my template',
162 });
163 });
164
165 await clickQuickCommit();
166
167 expectMessageSentToServer({
168 type: 'runOperation',
169 operation: {
170 args: ['commit', '--addremove', '--message', expect.stringContaining('Template Title')],
171 id: expect.anything(),
172 runner: CommandRunner.Sapling,
173 trackEventName: 'CommitOperation',
174 },
175 });
176 });
177
178 it('clears quick commit title after committing', async () => {
179 const commitTree = screen.getByTestId('commit-tree-root');
180 clickCheckboxForFile(commitTree, 'file2.txt'); // partial commit, so the quick input box isn't unmounted
181
182 const quickInput = screen.getByTestId('quick-commit-title');
183 act(() => {
184 userEvent.type(quickInput, 'My Commit');
185 });
186
187 await clickQuickCommit();
188
189 expect(quickInput).toHaveValue('');
190 });
191
192 it('on error, restores edited commit message to try again', async () => {
193 act(() => openCommitInfoSidebar());
194 act(() => CommitInfoTestUtils.clickCommitMode());
195
196 act(() => {
197 const title = CommitInfoTestUtils.getTitleEditor();
198 userEvent.type(title, 'My Commit');
199 const desc = CommitInfoTestUtils.getDescriptionEditor();
200 userEvent.type(desc, 'My description');
201 });
202
203 await CommitInfoTestUtils.clickCommitButton();
204 const message = await waitFor(() =>
205 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
206 );
207 const id = message.operation.id;
208
209 CommitInfoTestUtils.expectIsNOTEditingTitle();
210
211 act(() => {
212 simulateMessageFromServer({
213 type: 'operationProgress',
214 kind: 'exit',
215 exitCode: 1,
216 id,
217 timestamp: 0,
218 });
219 });
220
221 await waitFor(() => {
222 CommitInfoTestUtils.expectIsEditingTitle();
223 const title = CommitInfoTestUtils.getTitleEditor();
224 expect(title).toHaveValue('My Commit');
225 CommitInfoTestUtils.expectIsEditingDescription();
226 const desc = CommitInfoTestUtils.getDescriptionEditor();
227 expect(desc.value).toContain('My description');
228 });
229 });
230
231 it('on error, merges messages when restoring edited commit message to try again', async () => {
232 act(() => openCommitInfoSidebar());
233 act(() => CommitInfoTestUtils.clickCommitMode());
234
235 act(() => {
236 const title = CommitInfoTestUtils.getTitleEditor();
237 userEvent.type(title, 'My Commit');
238 const desc = CommitInfoTestUtils.getDescriptionEditor();
239 userEvent.type(desc, 'My description');
240 });
241
242 await CommitInfoTestUtils.clickCommitButton();
243 const message = await waitFor(() =>
244 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
245 );
246 const id = message.operation.id;
247
248 CommitInfoTestUtils.expectIsNOTEditingTitle();
249
250 act(() => {
251 openCommitInfoSidebar();
252 CommitInfoTestUtils.clickCommitMode();
253 });
254 act(() => {
255 const title = CommitInfoTestUtils.getTitleEditor();
256 userEvent.type(title, 'other title');
257 const desc = CommitInfoTestUtils.getDescriptionEditor();
258 userEvent.type(desc, 'other description');
259 });
260
261 act(() => {
262 simulateMessageFromServer({
263 type: 'operationProgress',
264 kind: 'exit',
265 exitCode: 1,
266 id,
267 timestamp: 0,
268 });
269 });
270
271 await waitFor(() => {
272 CommitInfoTestUtils.expectIsEditingTitle();
273 const title = CommitInfoTestUtils.getTitleEditor();
274 expect(title).toHaveValue('other title, My Commit');
275 CommitInfoTestUtils.expectIsEditingDescription();
276 const desc = CommitInfoTestUtils.getDescriptionEditor();
277 expect(desc.value).toContain('other description');
278 expect(desc.value).toContain('My description');
279 });
280 });
281});
282