addons/isl/src/__tests__/operations/revertOperation.test.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import {act, fireEvent, render, screen, within} from '@testing-library/react';
b69ab319import {nextTick} from 'shared/testUtils';
b69ab3110import App from '../../App';
b69ab3111import platform from '../../platform';
b69ab3112import {CommitInfoTestUtils, CommitTreeListTestUtils, ignoreRTL} from '../../testQueries';
b69ab3113import {
b69ab3114 COMMIT,
b69ab3115 expectMessageNOTSentToServer,
b69ab3116 expectMessageSentToServer,
b69ab3117 resetTestMessages,
b69ab3118 simulateCommits,
b69ab3119 simulateUncommittedChangedFiles,
b69ab3120} from '../../testUtils';
b69ab3121import {CommandRunner} from '../../types';
b69ab3122
b69ab3123/* eslint-disable require-await */
b69ab3124
b69ab3125describe('RevertOperation', () => {
b69ab3126 beforeEach(() => {
b69ab3127 resetTestMessages();
b69ab3128 render(<App />);
b69ab3129 act(() => {
b69ab3130 expectMessageSentToServer({
b69ab3131 type: 'subscribe',
b69ab3132 kind: 'smartlogCommits',
b69ab3133 subscriptionID: expect.anything(),
b69ab3134 });
b69ab3135 simulateCommits({
b69ab3136 value: [
b69ab3137 COMMIT('c', 'Commit C', 'b', {
b69ab3138 filePathsSample: ['file.txt'],
b69ab3139 totalFileCount: 1,
b69ab3140 isDot: true,
b69ab3141 }),
b69ab3142 COMMIT('b', 'Commit B', 'a', {filePathsSample: ['file.txt'], totalFileCount: 1}),
b69ab3143 COMMIT('a', 'Commit A', '1', {filePathsSample: ['file.txt'], totalFileCount: 1}),
b69ab3144 COMMIT('1', 'Commit 1', '0', {phase: 'public'}),
b69ab3145 ],
b69ab3146 });
b69ab3147 });
b69ab3148
b69ab3149 // confirm all prompts about reverting files
b69ab3150 jest.spyOn(platform, 'confirm').mockImplementation(() => Promise.resolve(true));
b69ab3151 });
b69ab3152
b69ab3153 const clickRevert = async (inside: HTMLElement, fileName: string) => {
b69ab3154 await act(async () => {
b69ab3155 const revertButton = within(
b69ab3156 within(inside).getByTestId(`changed-file-${fileName}`),
b69ab3157 ).getByTestId('file-revert-button');
b69ab3158 expect(revertButton).toBeInTheDocument();
b69ab3159 fireEvent.click(revertButton);
b69ab3160 // confirm modal takes 1 tick to resolve
b69ab3161 await nextTick();
b69ab3162 });
b69ab3163 };
b69ab3164
b69ab3165 const clickDelete = async (inside: HTMLElement, fileName: string) => {
b69ab3166 await act(async () => {
b69ab3167 const revertButton = within(
b69ab3168 within(inside).getByTestId(`changed-file-${fileName}`),
b69ab3169 ).getByTestId('file-action-delete');
b69ab3170 expect(revertButton).toBeInTheDocument();
b69ab3171 fireEvent.click(revertButton);
b69ab3172 // confirm modal takes 1 tick to resolve
b69ab3173 await nextTick();
b69ab3174 });
b69ab3175 };
b69ab3176
b69ab3177 const clickCheckboxForFile = async (inside: HTMLElement, fileName: string) => {
b69ab3178 await act(async () => {
b69ab3179 const checkbox = within(within(inside).getByTestId(`changed-file-${fileName}`)).getByTestId(
b69ab3180 'file-selection-checkbox',
b69ab3181 );
b69ab3182 expect(checkbox).toBeInTheDocument();
b69ab3183 fireEvent.click(checkbox);
b69ab3184 });
b69ab3185 };
b69ab3186
b69ab3187 describe('from uncommitted changes', () => {
b69ab3188 beforeEach(() => {
b69ab3189 act(() => {
b69ab3190 simulateUncommittedChangedFiles({
b69ab3191 value: [
b69ab3192 {path: 'myFile1.txt', status: 'M'},
b69ab3193 {path: 'myFile2.txt', status: 'M'},
b69ab3194 ],
b69ab3195 });
b69ab3196 });
b69ab3197 });
b69ab3198
b69ab3199 it('runs revert from uncommitted changes', async () => {
b69ab31100 await clickRevert(screen.getByTestId('commit-tree-root'), 'myFile1.txt');
b69ab31101
b69ab31102 expectMessageSentToServer({
b69ab31103 type: 'runOperation',
b69ab31104 operation: {
b69ab31105 args: ['revert', {type: 'repo-relative-file-list', paths: ['myFile1.txt']}],
b69ab31106 id: expect.anything(),
b69ab31107 runner: CommandRunner.Sapling,
b69ab31108 trackEventName: 'RevertOperation',
b69ab31109 },
b69ab31110 });
b69ab31111 });
b69ab31112
b69ab31113 it('renders optimistic state while running revert', async () => {
b69ab31114 expect(
b69ab31115 CommitTreeListTestUtils.withinCommitTree().getByText(ignoreRTL('myFile1.txt')),
b69ab31116 ).toBeInTheDocument();
b69ab31117 await clickRevert(screen.getByTestId('commit-tree-root'), 'myFile1.txt');
b69ab31118 expect(
b69ab31119 CommitTreeListTestUtils.withinCommitTree().queryByText(ignoreRTL('myFile1.txt')),
b69ab31120 ).not.toBeInTheDocument();
b69ab31121 });
b69ab31122
b69ab31123 describe('untracked files get purged', () => {
b69ab31124 beforeEach(() => {
b69ab31125 act(() => {
b69ab31126 simulateUncommittedChangedFiles({
b69ab31127 value: [
b69ab31128 {path: 'myFile1.txt', status: 'M'},
b69ab31129 {path: 'untracked.txt', status: '?'},
b69ab31130 ],
b69ab31131 });
b69ab31132 });
b69ab31133 });
b69ab31134
b69ab31135 it('runs purge for untracked uncommitted changes', async () => {
b69ab31136 await clickDelete(screen.getByTestId('commit-tree-root'), 'untracked.txt');
b69ab31137
b69ab31138 expectMessageSentToServer({
b69ab31139 type: 'runOperation',
b69ab31140 operation: {
b69ab31141 args: [
b69ab31142 'purge',
b69ab31143 '--files',
b69ab31144 '--abort-on-err',
b69ab31145 {type: 'repo-relative-file-list', paths: ['untracked.txt']},
b69ab31146 ],
b69ab31147 id: expect.anything(),
b69ab31148 runner: CommandRunner.Sapling,
b69ab31149 trackEventName: 'PurgeOperation',
b69ab31150 },
b69ab31151 });
b69ab31152 });
b69ab31153
b69ab31154 it('renders optimistic state while running purge', async () => {
b69ab31155 expect(
b69ab31156 CommitTreeListTestUtils.withinCommitTree().getByText(ignoreRTL('untracked.txt')),
b69ab31157 ).toBeInTheDocument();
b69ab31158 await clickDelete(screen.getByTestId('commit-tree-root'), 'untracked.txt');
b69ab31159 expect(
b69ab31160 CommitTreeListTestUtils.withinCommitTree().queryByText(ignoreRTL('untracked.txt')),
b69ab31161 ).not.toBeInTheDocument();
b69ab31162 });
b69ab31163 });
b69ab31164 });
b69ab31165
b69ab31166 describe('bulk discard', () => {
b69ab31167 let confirmSpy: jest.SpyInstance;
b69ab31168 beforeEach(() => {
b69ab31169 confirmSpy = jest.spyOn(platform, 'confirm').mockImplementation(() => Promise.resolve(true));
b69ab31170 act(() => {
b69ab31171 simulateUncommittedChangedFiles({
b69ab31172 value: [
b69ab31173 {path: 'myFile1.txt', status: 'M'},
b69ab31174 {path: 'myFile2.txt', status: 'M'},
b69ab31175 {path: 'untracked1.txt', status: '?'},
b69ab31176 {path: 'untracked2.txt', status: '?'},
b69ab31177 ],
b69ab31178 });
b69ab31179 });
b69ab31180 });
b69ab31181
b69ab31182 it('discards all changes with goto --clean if everything selected', async () => {
b69ab31183 await act(async () => {
b69ab31184 fireEvent.click(
b69ab31185 within(screen.getByTestId('commit-tree-root')).getByTestId('discard-all-selected-button'),
b69ab31186 );
b69ab31187 });
b69ab31188
b69ab31189 expectMessageSentToServer({
b69ab31190 type: 'runOperation',
b69ab31191 operation: {
b69ab31192 args: ['goto', '--clean', '.'],
b69ab31193 id: expect.anything(),
b69ab31194 runner: CommandRunner.Sapling,
b69ab31195 trackEventName: 'DiscardOperation',
b69ab31196 },
b69ab31197 });
b69ab31198
b69ab31199 expectMessageSentToServer({
b69ab31200 type: 'runOperation',
b69ab31201 operation: {
b69ab31202 args: ['purge', '--files', '--abort-on-err'],
b69ab31203 id: expect.anything(),
b69ab31204 runner: CommandRunner.Sapling,
b69ab31205 trackEventName: 'PurgeOperation',
b69ab31206 },
b69ab31207 });
b69ab31208
b69ab31209 expect(confirmSpy).toHaveBeenCalled();
b69ab31210 });
b69ab31211
b69ab31212 it('discards selected changes with revert and purge', async () => {
b69ab31213 const commitTree = screen.getByTestId('commit-tree-root');
b69ab31214 await clickCheckboxForFile(commitTree, 'myFile1.txt');
b69ab31215 await clickCheckboxForFile(commitTree, 'untracked1.txt');
b69ab31216
b69ab31217 await act(async () => {
b69ab31218 fireEvent.click(
b69ab31219 within(screen.getByTestId('commit-tree-root')).getByTestId('discard-all-selected-button'),
b69ab31220 );
b69ab31221 });
b69ab31222
b69ab31223 expectMessageSentToServer({
b69ab31224 type: 'runOperation',
b69ab31225 operation: {
b69ab31226 args: ['revert', {type: 'repo-relative-file-list', paths: ['myFile2.txt']}],
b69ab31227 id: expect.anything(),
b69ab31228 runner: CommandRunner.Sapling,
b69ab31229 trackEventName: 'RevertOperation',
b69ab31230 },
b69ab31231 });
b69ab31232
b69ab31233 expectMessageSentToServer({
b69ab31234 type: 'runOperation',
b69ab31235 operation: {
b69ab31236 args: [
b69ab31237 'purge',
b69ab31238 '--files',
b69ab31239 '--abort-on-err',
b69ab31240 {type: 'repo-relative-file-list', paths: ['untracked2.txt']},
b69ab31241 ],
b69ab31242 id: expect.anything(),
b69ab31243 runner: CommandRunner.Sapling,
b69ab31244 trackEventName: 'PurgeOperation',
b69ab31245 },
b69ab31246 });
b69ab31247
b69ab31248 expect(confirmSpy).toHaveBeenCalled();
b69ab31249 });
b69ab31250
b69ab31251 it('uses purge for added and renamed files for selected changes', async () => {
b69ab31252 act(() => {
b69ab31253 simulateUncommittedChangedFiles({
b69ab31254 value: [
b69ab31255 {path: 'myFile1.txt', status: 'A'},
b69ab31256 {path: 'myFile2.txt', status: 'A'},
b69ab31257 {path: 'movedFrom.txt', status: 'R'},
b69ab31258 {path: 'movedTo.txt', status: 'A', copy: 'movedFrom.txt'},
b69ab31259 ],
b69ab31260 });
b69ab31261 });
b69ab31262 const commitTree = screen.getByTestId('commit-tree-root');
b69ab31263 await clickCheckboxForFile(commitTree, 'myFile2.txt');
b69ab31264
b69ab31265 await act(async () => {
b69ab31266 fireEvent.click(
b69ab31267 within(screen.getByTestId('commit-tree-root')).getByTestId('discard-all-selected-button'),
b69ab31268 );
b69ab31269 });
b69ab31270
b69ab31271 expectMessageSentToServer({
b69ab31272 type: 'runOperation',
b69ab31273 operation: {
b69ab31274 args: [
b69ab31275 'revert',
b69ab31276 {
b69ab31277 type: 'repo-relative-file-list',
b69ab31278 paths: ['myFile1.txt', 'movedFrom.txt', 'movedTo.txt'],
b69ab31279 },
b69ab31280 ],
b69ab31281 id: expect.anything(),
b69ab31282 runner: CommandRunner.Sapling,
b69ab31283 trackEventName: 'RevertOperation',
b69ab31284 },
b69ab31285 });
b69ab31286
b69ab31287 expectMessageSentToServer({
b69ab31288 type: 'runOperation',
b69ab31289 operation: {
b69ab31290 args: [
b69ab31291 'purge',
b69ab31292 '--files',
b69ab31293 '--abort-on-err',
b69ab31294 {type: 'repo-relative-file-list', paths: ['myFile1.txt', 'movedTo.txt']},
b69ab31295 ],
b69ab31296 id: expect.anything(),
b69ab31297 runner: CommandRunner.Sapling,
b69ab31298 trackEventName: 'PurgeOperation',
b69ab31299 },
b69ab31300 });
b69ab31301
b69ab31302 expect(confirmSpy).toHaveBeenCalled();
b69ab31303 });
b69ab31304
b69ab31305 it('no need to run purge if no files are untracked', async () => {
b69ab31306 const commitTree = screen.getByTestId('commit-tree-root');
b69ab31307 await clickCheckboxForFile(commitTree, 'untracked1.txt');
b69ab31308 await clickCheckboxForFile(commitTree, 'untracked2.txt');
b69ab31309
b69ab31310 await act(async () => {
b69ab31311 fireEvent.click(
b69ab31312 within(screen.getByTestId('commit-tree-root')).getByTestId('discard-all-selected-button'),
b69ab31313 );
b69ab31314 });
b69ab31315
b69ab31316 expectMessageSentToServer({
b69ab31317 type: 'runOperation',
b69ab31318 operation: {
b69ab31319 args: [
b69ab31320 'revert',
b69ab31321 {type: 'repo-relative-file-list', paths: ['myFile1.txt', 'myFile2.txt']},
b69ab31322 ],
b69ab31323 id: expect.anything(),
b69ab31324 runner: CommandRunner.Sapling,
b69ab31325 trackEventName: 'RevertOperation',
b69ab31326 },
b69ab31327 });
b69ab31328
b69ab31329 expectMessageNOTSentToServer({
b69ab31330 type: 'runOperation',
b69ab31331 operation: {
b69ab31332 args: expect.arrayContaining(['purge', '--files']),
b69ab31333 id: expect.anything(),
b69ab31334 runner: CommandRunner.Sapling,
b69ab31335 trackEventName: expect.anything(),
b69ab31336 },
b69ab31337 });
b69ab31338
b69ab31339 expect(confirmSpy).toHaveBeenCalled();
b69ab31340 });
b69ab31341 });
b69ab31342
b69ab31343 describe('in commit info view for a given commit', () => {
b69ab31344 it('hides revert button on non-head commits', () => {
b69ab31345 CommitInfoTestUtils.clickToSelectCommit('a');
b69ab31346
b69ab31347 const revertButton = within(
b69ab31348 within(screen.getByTestId('commit-info-view')).getByTestId(`changed-file-file.txt`),
b69ab31349 ).queryByTestId('file-revert-button');
b69ab31350 expect(revertButton).not.toBeInTheDocument();
b69ab31351 });
b69ab31352
b69ab31353 it('reverts before head commit', async () => {
b69ab31354 CommitInfoTestUtils.clickToSelectCommit('c');
b69ab31355 await clickRevert(screen.getByTestId('commit-info-view'), 'file.txt');
b69ab31356
b69ab31357 expectMessageSentToServer({
b69ab31358 type: 'runOperation',
b69ab31359 operation: {
b69ab31360 args: [
b69ab31361 'revert',
b69ab31362 '--rev',
b69ab31363 {type: 'succeedable-revset', revset: '.^'},
b69ab31364 {type: 'repo-relative-file-list', paths: ['file.txt']},
b69ab31365 ],
b69ab31366 id: expect.anything(),
b69ab31367 runner: CommandRunner.Sapling,
b69ab31368 trackEventName: 'RevertOperation',
b69ab31369 },
b69ab31370 });
b69ab31371 });
b69ab31372
b69ab31373 it('renders optimistic state while running', async () => {
b69ab31374 CommitInfoTestUtils.clickToSelectCommit('c');
b69ab31375 expect(
b69ab31376 CommitTreeListTestUtils.withinCommitTree().queryByText(ignoreRTL('file.txt')),
b69ab31377 ).not.toBeInTheDocument();
b69ab31378
b69ab31379 await clickRevert(screen.getByTestId('commit-info-view'), 'file.txt');
b69ab31380
b69ab31381 // file is not hidden from the tree, instead it's inserted
b69ab31382 expect(
b69ab31383 CommitTreeListTestUtils.withinCommitTree().getByText(ignoreRTL('file.txt')),
b69ab31384 ).toBeInTheDocument();
b69ab31385 });
b69ab31386 });
b69ab31387});