6.4 KB232 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 type {DateTuple} from 'shared/types/common';
9import type {CodeReviewSystem} from '../types';
10
11import {act, fireEvent, render, screen, waitFor, within} from '@testing-library/react';
12import * as utils from 'shared/utils';
13import App from '../App';
14import {
15 closeCommitInfoSidebar,
16 expectMessageNOTSentToServer,
17 expectMessageSentToServer,
18 getLastMessageOfTypeSentToServer,
19 resetTestMessages,
20 simulateCommits,
21 simulateMessageFromServer,
22 TEST_COMMIT_HISTORY,
23} from '../testUtils';
24import {CommandRunner} from '../types';
25
26const EXPORT_STACK_DATA = [
27 {
28 requested: false,
29 node: 'd',
30 author: 'username',
31 date: [1715719789, 25200] as DateTuple,
32 text: 'Commit D',
33 immutable: false,
34 relevantFiles: {
35 'myFile.js': {
36 data: 'hello\nworld!\n',
37 },
38 },
39 },
40 {
41 requested: true,
42 node: 'e',
43 author: 'username',
44 date: [1715719789, 25200] as DateTuple,
45 text: 'Commit E',
46 immutable: false,
47 parents: ['d'],
48 files: {
49 'myFile.js': {
50 data: 'hello (changed)\nworld!\n',
51 },
52 },
53 },
54];
55
56describe('Interactive Split', () => {
57 beforeEach(() => {
58 resetTestMessages();
59 render(<App />);
60 act(() => {
61 closeCommitInfoSidebar();
62 simulateMessageFromServer({
63 type: 'repoInfo',
64 info: {
65 type: 'success',
66 repoRoot: '/path/to/repo',
67 dotdir: '/path/to/repo/.sl',
68 command: 'sl',
69 pullRequestDomain: undefined,
70 codeReviewSystem: {type: 'github'} as CodeReviewSystem,
71 isEdenFs: false,
72 },
73 });
74 expectMessageSentToServer({
75 type: 'subscribe',
76 kind: 'smartlogCommits',
77 subscriptionID: expect.anything(),
78 });
79 simulateCommits({
80 value: TEST_COMMIT_HISTORY,
81 });
82 });
83
84 const mockObserveFn = () => {
85 return {
86 observe: jest.fn(),
87 unobserve: jest.fn(),
88 disconnect: jest.fn(),
89 };
90 };
91
92 window.IntersectionObserver = jest.fn().mockImplementation(mockObserveFn);
93 });
94
95 it('shows split button on dot commit', () => {
96 expect(screen.getByText('Split')).toBeInTheDocument();
97 });
98
99 it('show split modal with spinner on click', async () => {
100 fireEvent.click(screen.getByText('Split'));
101 await waitFor(() => expect(screen.getByTestId('edit-stack-loading')).toBeInTheDocument());
102 });
103
104 it('requests debugexportstack data', async () => {
105 fireEvent.click(screen.getByText('Split'));
106
107 await waitFor(() => expectMessageSentToServer({type: 'exportStack', revs: 'e'}));
108 });
109
110 it('shows errors', async () => {
111 fireEvent.click(screen.getByText('Split'));
112 await waitFor(() => expectMessageSentToServer({type: 'exportStack', revs: 'e'}));
113 act(() => {
114 simulateMessageFromServer({
115 type: 'exportedStack',
116 revs: 'e',
117 assumeTracked: [],
118 error: 'test error',
119 stack: [],
120 });
121 });
122 await waitFor(() => expect(screen.getByText('test error')).toBeInTheDocument());
123 });
124
125 it('waits for existing commands to finish running before loading stack', async () => {
126 fireEvent.click(screen.getByText('Pull', {selector: 'button'}));
127 const message = await waitFor(() =>
128 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
129 );
130 const id = message.operation.id;
131
132 fireEvent.click(screen.getByText('Split'));
133
134 expectMessageNOTSentToServer({type: 'exportStack', revs: 'e'});
135
136 act(() =>
137 simulateMessageFromServer({
138 type: 'operationProgress',
139 id,
140 kind: 'exit',
141 exitCode: 0,
142 timestamp: 0,
143 }),
144 );
145
146 await waitFor(() => expectMessageSentToServer({type: 'exportStack', revs: 'e'}));
147 });
148
149 describe('with loaded stack data', () => {
150 beforeEach(async () => {
151 fireEvent.click(screen.getByText('Split'));
152 await waitFor(() => expectMessageSentToServer({type: 'exportStack', revs: 'e'}));
153 act(() => {
154 simulateMessageFromServer({
155 type: 'exportedStack',
156 revs: 'e',
157 assumeTracked: [],
158 error: undefined,
159 stack: EXPORT_STACK_DATA,
160 });
161 });
162 await waitFor(() =>
163 expect(screen.getByTestId('interactive-split-modal')).toBeInTheDocument(),
164 );
165 });
166
167 it('loads exported stack into UI', () => {
168 expect(
169 within(screen.getByTestId('interactive-split-modal')).getByText('myFile.js'),
170 ).toBeInTheDocument();
171
172 expect(
173 within(screen.getByTestId('interactive-split-modal')).getByText('hello'),
174 ).toBeInTheDocument();
175
176 expect(
177 within(screen.getByTestId('interactive-split-modal')).getByText('hello (changed)'),
178 ).toBeInTheDocument();
179 });
180
181 it('moves lines and requests importing', async () => {
182 jest.useFakeTimers().setSystemTime(new Date('2020-01-01'));
183
184 const arrows = screen.getAllByTitle('Move this line change right');
185 fireEvent.click(arrows[1]);
186 fireEvent.click(screen.getByTestId('confirm-edit-stack-button'));
187
188 const message = await waitFor(() =>
189 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
190 );
191 const id = message.operation.id;
192
193 expectMessageSentToServer({
194 type: 'runOperation',
195 operation: {
196 id,
197 trackEventName: 'ImportStackOperation',
198 args: ['debugimportstack'],
199 stdin: JSON.stringify([
200 [
201 'commit',
202 {
203 mark: ':r1',
204 author: 'username',
205 date: [1577836800, 25200],
206 text: 'Commit E',
207 parents: ['d'],
208 predecessors: ['e'],
209 files: {'myFile.js': {data: 'world!\n', flags: ''}},
210 },
211 ],
212 [
213 'commit',
214 {
215 mark: ':r2',
216 author: 'username',
217 date: [1577836800, 25200],
218 text: 'Split of "Commit E"',
219 parents: [':r1'],
220 predecessors: ['e'],
221 files: {'myFile.js': {data: 'hello (changed)\nworld!\n', flags: ''}},
222 },
223 ],
224 ['goto', {mark: ':r2'}],
225 ]),
226 runner: CommandRunner.Sapling,
227 },
228 });
229 });
230 });
231});
232