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