6.9 KB212 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 {Hash} from '../../types';
9
10import {act, fireEvent, render, screen} from '@testing-library/react';
11import {nextTick} from 'shared/testUtils';
12import App from '../../App';
13import platform from '../../platform';
14import {
15 COMMIT,
16 closeCommitInfoSidebar,
17 expectMessageNOTSentToServer,
18 expectMessageSentToServer,
19 expectYouAreHerePointAt,
20 resetTestMessages,
21 simulateCommits,
22} from '../../testUtils';
23import {CommandRunner, succeedableRevset} from '../../types';
24
25describe('GotoOperation', () => {
26 beforeEach(() => {
27 resetTestMessages();
28 render(<App />);
29 act(() => {
30 closeCommitInfoSidebar();
31 expectMessageSentToServer({
32 type: 'subscribe',
33 kind: 'smartlogCommits',
34 subscriptionID: expect.anything(),
35 });
36 simulateCommits({
37 value: [
38 COMMIT('2', 'master', '00', {phase: 'public', remoteBookmarks: ['remote/master']}),
39 COMMIT('1', 'Commit 1', '0', {phase: 'public'}),
40 COMMIT('a', 'Commit A', '1'),
41 COMMIT('b', 'Commit B', 'a', {isDot: true}),
42 COMMIT('c', 'Commit C', 'b'),
43 ],
44 });
45 });
46 });
47
48 const clickGoto = async (commit: Hash) => {
49 const myCommit = screen.queryByTestId(`commit-${commit}`);
50 const gotoButton = myCommit?.querySelector('.goto-button button');
51 expect(gotoButton).toBeDefined();
52 await act(async () => {
53 fireEvent.click(gotoButton as Element);
54 await nextTick(); // async check if commit is too old
55 });
56 };
57
58 it('goto button is accessible', () => {
59 expect(screen.getByLabelText('Go to commit "Commit A"')).toBeInTheDocument();
60 expect(screen.queryByLabelText('Go to commit "Commit B"')).not.toBeInTheDocument(); // already head, no goto button
61 expect(screen.getByLabelText('Go to commit "Commit C"')).toBeInTheDocument();
62 });
63
64 it('runs goto', async () => {
65 await clickGoto('a');
66
67 expectMessageSentToServer({
68 type: 'runOperation',
69 operation: {
70 args: ['goto', '--rev', succeedableRevset('a')],
71 id: expect.anything(),
72 runner: CommandRunner.Sapling,
73 trackEventName: 'GotoOperation',
74 },
75 });
76 });
77
78 it('renders optimistic state while running', async () => {
79 await clickGoto('a');
80
81 expectYouAreHerePointAt('a');
82 });
83
84 it('optimistic state resolves after goto completes', async () => {
85 await clickGoto('a');
86
87 act(() => {
88 simulateCommits({
89 value: [
90 COMMIT('1', 'Commit 1', '0', {phase: 'public'}),
91 COMMIT('a', 'Commit A', '1', {isDot: true}),
92 COMMIT('b', 'Commit B', 'a'),
93 COMMIT('c', 'Commit C', 'b'),
94 ],
95 });
96 });
97
98 // With the DAG renderer, we no longer show old "were here" and new "moving here".
99 // The idea is that there would be a spinner on the "status" calculation to indicate
100 // the in-progress checkout. For now this test looks the same as the above.
101 expectYouAreHerePointAt('a');
102 });
103
104 describe('bookmarks as destinations', () => {
105 it('runs goto with bookmark', async () => {
106 await clickGoto('2');
107
108 expectMessageSentToServer({
109 type: 'runOperation',
110 operation: {
111 args: ['goto', '--rev', succeedableRevset('remote/master')],
112 id: expect.anything(),
113 runner: CommandRunner.Sapling,
114 trackEventName: 'GotoOperation',
115 },
116 });
117 });
118
119 it('renders optimistic state while running', async () => {
120 await clickGoto('2');
121
122 expectYouAreHerePointAt('2');
123 });
124 });
125
126 describe('succession', () => {
127 it('handles successions', async () => {
128 await clickGoto('c');
129
130 // get a new batch of commits from some other operation like rebase, which
131 // rewrites a,b,c into a1,b2,c2
132 act(() => {
133 simulateCommits({
134 value: [
135 COMMIT('2', 'master', '00', {phase: 'public', remoteBookmarks: ['remote/master']}),
136 COMMIT('1', 'Commit 1', '0', {phase: 'public'}),
137 COMMIT('a2', 'Commit A', '1', {closestPredecessors: ['a']}),
138 COMMIT('b2', 'Commit B', 'a2', {isDot: true, closestPredecessors: ['b']}),
139 COMMIT('c2', 'Commit C', 'b2', {closestPredecessors: ['c']}),
140 ],
141 });
142 });
143
144 // "c" becomes "c2"
145 expectYouAreHerePointAt('c2');
146 });
147 });
148
149 describe('age warning', () => {
150 let confirmSpy: jest.SpyInstance;
151 beforeEach(() => {
152 confirmSpy = jest.spyOn(platform, 'confirm').mockImplementation(() => Promise.resolve(true));
153 act(() => {
154 simulateCommits({
155 value: [
156 COMMIT('b', 'Commit B', 'a', {isDot: true, date: new Date('2024-03-04')}),
157 COMMIT('a', 'Commit A', '3', {date: new Date('2024-03-03')}),
158 COMMIT('3', 'Commit 3', '003', {phase: 'public', date: new Date('2024-03-02')}),
159 COMMIT('2', 'Commit 2', '002', {phase: 'public', date: new Date('2024-03-01')}),
160 COMMIT('x', 'Commit X', '1', {date: new Date('2024-03-03')}),
161 COMMIT('1', 'Commit 1', '001', {phase: 'public', date: new Date('2020-01-01')}),
162 ],
163 });
164 });
165 });
166
167 it('warns if going to an old commit', async () => {
168 await clickGoto('1');
169 expect(confirmSpy).toHaveBeenCalled();
170 });
171
172 it("cancels goto if you don't confirm", async () => {
173 confirmSpy = jest.spyOn(platform, 'confirm').mockImplementation(() => Promise.resolve(false));
174 await clickGoto('1');
175 expect(confirmSpy).toHaveBeenCalled();
176 expectMessageNOTSentToServer({
177 type: 'runOperation',
178 operation: expect.objectContaining({
179 args: expect.arrayContaining(['goto']),
180 }),
181 });
182 });
183
184 it('does not warn for short goto', async () => {
185 await clickGoto('a');
186 expect(confirmSpy).not.toHaveBeenCalled();
187 });
188
189 it('compares base public commit, not destination itself', async () => {
190 await clickGoto('x'); // x is only 1 day old, but its parent is months older than b's public base.
191 expect(confirmSpy).toHaveBeenCalled();
192 });
193
194 it('only warns going backwards, not forwards', async () => {
195 act(() => {
196 simulateCommits({
197 value: [
198 COMMIT('b', 'Commit B', 'a', {date: new Date('2024-03-04')}),
199 COMMIT('a', 'Commit A', '3', {date: new Date('2024-03-03')}),
200 COMMIT('3', 'Commit 3', '003', {phase: 'public', date: new Date('2024-03-02')}),
201 COMMIT('2', 'Commit 2', '002', {phase: 'public', date: new Date('2024-03-01')}),
202 COMMIT('x', 'Commit X', '1', {isDot: true, date: new Date('2024-03-03')}),
203 COMMIT('1', 'Commit 1', '001', {phase: 'public', date: new Date('2020-01-01')}),
204 ],
205 });
206 });
207 await clickGoto('b');
208 expect(confirmSpy).not.toHaveBeenCalled();
209 });
210 });
211});
212