addons/isl/src/__tests__/CommitInfoView.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, waitFor, within} from '@testing-library/react';
b69ab319import userEvent from '@testing-library/user-event';
b69ab3110import App from '../App';
b69ab3111import {tracker} from '../analytics';
b69ab3112import platform from '../platform';
b69ab3113import {CommitInfoTestUtils, CommitTreeListTestUtils, ignoreRTL} from '../testQueries';
b69ab3114import {
b69ab3115 COMMIT,
b69ab3116 closeCommitInfoSidebar,
b69ab3117 expectMessageSentToServer,
b69ab3118 openCommitInfoSidebar,
b69ab3119 resetTestMessages,
b69ab3120 simulateCommits,
b69ab3121 simulateMessageFromServer,
b69ab3122 simulateUncommittedChangedFiles,
b69ab3123 waitForWithTick,
b69ab3124} from '../testUtils';
b69ab3125import {CommandRunner, succeedableRevset} from '../types';
b69ab3126
b69ab3127/* eslint-disable @typescript-eslint/no-non-null-assertion */
b69ab3128
b69ab3129const {
b69ab3130 withinCommitInfo,
b69ab3131 clickAmendButton,
b69ab3132 clickCancel,
b69ab3133 clickCommitButton,
b69ab3134 clickCommitMode,
b69ab3135 clickToSelectCommit,
b69ab3136 getTitleEditor,
b69ab3137 getDescriptionEditor,
b69ab3138 clickToEditTitle,
b69ab3139 clickToEditDescription,
b69ab3140 expectIsEditingTitle,
b69ab3141 expectIsNOTEditingTitle,
b69ab3142 expectIsEditingDescription,
b69ab3143 expectIsNOTEditingDescription,
b69ab3144} = CommitInfoTestUtils;
b69ab3145
b69ab3146describe('CommitInfoView', () => {
b69ab3147 beforeEach(() => {
b69ab3148 resetTestMessages();
b69ab3149 jest.spyOn(tracker, 'track').mockImplementation(() => undefined);
b69ab3150 });
b69ab3151
b69ab3152 it('shows loading spinner on mount', () => {
b69ab3153 render(<App />);
b69ab3154
b69ab3155 expect(screen.getByTestId('commit-info-view-loading')).toBeInTheDocument();
b69ab3156 });
b69ab3157
b69ab3158 describe('after commits loaded', () => {
b69ab3159 beforeEach(() => {
b69ab3160 render(<App />);
b69ab3161 act(() => {
b69ab3162 openCommitInfoSidebar();
b69ab3163 expectMessageSentToServer({
b69ab3164 type: 'subscribe',
b69ab3165 kind: 'smartlogCommits',
b69ab3166 subscriptionID: expect.anything(),
b69ab3167 });
b69ab3168 simulateCommits({
b69ab3169 value: [
b69ab3170 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab3171 COMMIT('a', 'My Commit', '1'),
b69ab3172 COMMIT('b', 'Head Commit', 'a', {isDot: true}),
b69ab3173 ],
b69ab3174 });
b69ab3175 });
b69ab3176 });
b69ab3177
b69ab3178 describe('drawer', () => {
b69ab3179 it('can close commit info sidebar by clicking label', () => {
b69ab3180 expect(screen.getByTestId('commit-info-view')).toBeInTheDocument();
b69ab3181 expect(screen.getByText('Commit Info')).toBeInTheDocument();
b69ab3182 act(() => {
b69ab3183 closeCommitInfoSidebar();
b69ab3184 });
b69ab3185 expect(screen.queryByTestId('commit-info-view')).not.toBeInTheDocument();
b69ab3186 expect(screen.getByText('Commit Info')).toBeInTheDocument();
b69ab3187 });
b69ab3188 });
b69ab3189
b69ab3190 describe('commit selection', () => {
b69ab3191 it('shows head commit by default', () => {
b69ab3192 expect(withinCommitInfo().queryByText('Head Commit')).toBeInTheDocument();
b69ab3193 });
b69ab3194
b69ab3195 it('can click to select commit', () => {
b69ab3196 clickToSelectCommit('a');
b69ab3197
b69ab3198 // now commit info view shows selected commit
b69ab3199 expect(withinCommitInfo().queryByText('My Commit')).toBeInTheDocument();
b69ab31100 expect(withinCommitInfo().queryByText('Head Commit')).not.toBeInTheDocument();
b69ab31101 });
b69ab31102
b69ab31103 it('cannot select public commits', () => {
b69ab31104 clickToSelectCommit('1');
b69ab31105
b69ab31106 expect(withinCommitInfo().queryByText('some public base')).not.toBeInTheDocument();
b69ab31107 // stays on head commit
b69ab31108 expect(withinCommitInfo().queryByText('Head Commit')).toBeInTheDocument();
b69ab31109 });
b69ab31110 });
b69ab31111
b69ab31112 describe('changed files', () => {
b69ab31113 beforeEach(() => {
b69ab31114 act(() => {
b69ab31115 simulateCommits({
b69ab31116 value: [
b69ab31117 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab31118 COMMIT('a', 'My Commit', '1', {filePathsSample: ['src/ca.js'], totalFileCount: 1}),
b69ab31119 COMMIT('b', 'Head Commit', 'a', {
b69ab31120 isDot: true,
b69ab31121 filePathsSample: ['src/cb.js'],
b69ab31122 totalFileCount: 1,
b69ab31123 }),
b69ab31124 ],
b69ab31125 });
b69ab31126 simulateUncommittedChangedFiles({
b69ab31127 value: [
b69ab31128 {path: 'src/file1.js', status: 'M'},
b69ab31129 {path: 'src/file2.js', status: 'M'},
b69ab31130 ],
b69ab31131 });
b69ab31132 });
b69ab31133 });
b69ab31134
b69ab31135 it('shows uncommitted changes for head commit', () => {
b69ab31136 expect(withinCommitInfo().queryByText(ignoreRTL('file1.js'))).toBeInTheDocument();
b69ab31137 expect(withinCommitInfo().queryByText(ignoreRTL('file2.js'))).toBeInTheDocument();
b69ab31138 });
b69ab31139
b69ab31140 it('shows file actions on uncommitted changes in commit info view', () => {
b69ab31141 // (1) uncommitted changes in commit list, (2) uncommitted changes in commit info, (3) committed changes in commit info
b69ab31142 expect(withinCommitInfo().queryAllByTestId('file-actions')).toHaveLength(3);
b69ab31143 });
b69ab31144
b69ab31145 it("doesn't show uncommitted changes on non-head commits ", () => {
b69ab31146 clickToSelectCommit('a');
b69ab31147 expect(withinCommitInfo().queryByText(ignoreRTL('file1.js'))).not.toBeInTheDocument();
b69ab31148 expect(withinCommitInfo().queryByText(ignoreRTL('file2.js'))).not.toBeInTheDocument();
b69ab31149 });
b69ab31150
b69ab31151 it('shows files changed in the commit for head commit', () => {
b69ab31152 expect(withinCommitInfo().queryByText(ignoreRTL('ca.js'))).not.toBeInTheDocument();
b69ab31153 expect(withinCommitInfo().queryByText(ignoreRTL('cb.js'))).toBeInTheDocument();
b69ab31154 });
b69ab31155
b69ab31156 it('shows files changed in the commit for non-head commit', () => {
b69ab31157 clickToSelectCommit('a');
b69ab31158 expect(withinCommitInfo().queryByText(ignoreRTL('ca.js'))).toBeInTheDocument();
b69ab31159 expect(withinCommitInfo().queryByText(ignoreRTL('cb.js'))).not.toBeInTheDocument();
b69ab31160 });
b69ab31161
b69ab31162 it('enables amend button with uncommitted changes', () => {
b69ab31163 expect(withinCommitInfo().queryByText(ignoreRTL('file1.js'))).toBeInTheDocument();
b69ab31164 expect(withinCommitInfo().queryByText(ignoreRTL('file2.js'))).toBeInTheDocument();
b69ab31165
b69ab31166 const amendButton: HTMLButtonElement | null = within(
b69ab31167 screen.getByTestId('commit-info-actions-bar'),
b69ab31168 ).queryByText('Amend');
b69ab31169 expect(amendButton).toBeInTheDocument();
b69ab31170 expect(amendButton?.disabled).not.toBe(true);
b69ab31171 });
b69ab31172
b69ab31173 it('does not show banner if all files are shown', () => {
b69ab31174 expect(
b69ab31175 withinCommitInfo().queryByText(/Showing first .* files out of .* total/),
b69ab31176 ).not.toBeInTheDocument();
b69ab31177 });
b69ab31178
b69ab31179 it('shows banner if not all files are shown', () => {
b69ab31180 act(() => {
b69ab31181 simulateCommits({
b69ab31182 value: [
b69ab31183 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab31184 COMMIT('a', 'Head Commit', '1', {
b69ab31185 isDot: true,
b69ab31186 filePathsSample: new Array(25).fill(null).map((_, i) => `src/file${i}.txt`),
b69ab31187 totalFileCount: 100,
b69ab31188 }),
b69ab31189 ],
b69ab31190 });
b69ab31191 simulateUncommittedChangedFiles({
b69ab31192 value: [],
b69ab31193 });
b69ab31194 });
b69ab31195
b69ab31196 expect(withinCommitInfo().queryByText(ignoreRTL('file1.txt'))).toBeInTheDocument();
b69ab31197 expect(
b69ab31198 withinCommitInfo().queryByText('Showing first 25 files out of 100 total'),
b69ab31199 ).toBeInTheDocument();
b69ab31200 });
b69ab31201
b69ab31202 it('runs amend with selected files', async () => {
b69ab31203 expect(withinCommitInfo().queryByText(ignoreRTL('file1.js'))).toBeInTheDocument();
b69ab31204 expect(withinCommitInfo().queryByText(ignoreRTL('file2.js'))).toBeInTheDocument();
b69ab31205
b69ab31206 act(() => {
b69ab31207 const checkboxes = withinCommitInfo()
b69ab31208 .queryByTestId('changes-to-amend')!
b69ab31209 .querySelectorAll('input[type="checkbox"]');
b69ab31210 fireEvent.click(checkboxes[0]);
b69ab31211 });
b69ab31212
b69ab31213 const amendButton = within(screen.getByTestId('commit-info-actions-bar')).queryByText(
b69ab31214 'Amend',
b69ab31215 );
b69ab31216 expect(amendButton).toBeInTheDocument();
b69ab31217
b69ab31218 act(() => {
b69ab31219 fireEvent.click(amendButton!);
b69ab31220 });
b69ab31221
b69ab31222 await waitFor(() =>
b69ab31223 expectMessageSentToServer({
b69ab31224 type: 'runOperation',
b69ab31225 operation: {
b69ab31226 args: [
b69ab31227 {type: 'config', key: 'amend.autorestack', value: 'always'},
b69ab31228 'amend',
b69ab31229 '--addremove',
b69ab31230 {type: 'repo-relative-file', path: 'src/file2.js'},
b69ab31231 '--message',
b69ab31232 expect.stringMatching(/^Head Commit/),
b69ab31233 ],
b69ab31234 id: expect.anything(),
b69ab31235 runner: CommandRunner.Sapling,
b69ab31236 trackEventName: 'AmendFileSubsetOperation',
b69ab31237 },
b69ab31238 }),
b69ab31239 );
b69ab31240 });
b69ab31241
b69ab31242 it('disallows amending when all uncommitted changes deselected', () => {
b69ab31243 // click every checkbox in changes to amend
b69ab31244 act(() => {
b69ab31245 const checkboxes = withinCommitInfo()
b69ab31246 .queryByTestId('changes-to-amend')
b69ab31247 ?.querySelectorAll('input[type="checkbox"]');
b69ab31248 checkboxes?.forEach(checkbox => {
b69ab31249 fireEvent.click(checkbox);
b69ab31250 });
b69ab31251 });
b69ab31252
b69ab31253 const amendButton: HTMLButtonElement | null = within(
b69ab31254 screen.getByTestId('commit-info-actions-bar'),
b69ab31255 ).queryByText('Amend');
b69ab31256 expect(amendButton).toBeInTheDocument();
b69ab31257 expect(amendButton?.disabled).toBe(true);
b69ab31258 });
b69ab31259
b69ab31260 it('shows optimistic uncommitted changes', async () => {
b69ab31261 act(() => {
b69ab31262 simulateUncommittedChangedFiles({
b69ab31263 value: [],
b69ab31264 });
b69ab31265 });
b69ab31266
b69ab31267 expect(screen.queryByText('Amend and Submit')).not.toBeInTheDocument();
b69ab31268
b69ab31269 jest.spyOn(platform, 'confirm').mockImplementation(() => Promise.resolve(true));
b69ab31270 act(() => {
b69ab31271 fireEvent.click(screen.getByText('Uncommit'));
b69ab31272 });
b69ab31273 act(() => {
b69ab31274 simulateMessageFromServer({
b69ab31275 type: 'fetchedCommitChangedFiles',
b69ab31276 hash: 'b',
b69ab31277 result: {
b69ab31278 value: {
b69ab31279 totalFileCount: 3,
b69ab31280 filesSample: [{path: 'src/cb.js', status: 'M'}],
b69ab31281 },
b69ab31282 },
b69ab31283 });
b69ab31284 });
b69ab31285
b69ab31286 await waitFor(() => {
b69ab31287 expect(withinCommitInfo().queryByText(ignoreRTL('cb.js'))).toBeInTheDocument();
b69ab31288 expect(screen.queryByText('Amend and Submit')).toBeInTheDocument();
b69ab31289 });
b69ab31290 });
b69ab31291 });
b69ab31292
b69ab31293 describe('editing fields', () => {
b69ab31294 beforeEach(() => {
b69ab31295 act(() => {
b69ab31296 simulateCommits({
b69ab31297 value: [
b69ab31298 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab31299 COMMIT('a', 'My Commit', '1', {description: 'Summary: First commit in the stack'}),
b69ab31300 COMMIT('b', 'Head Commit', 'a', {
b69ab31301 description: 'Summary: stacked commit',
b69ab31302 isDot: true,
b69ab31303 }),
b69ab31304 ],
b69ab31305 });
b69ab31306 });
b69ab31307 });
b69ab31308
b69ab31309 it('starts editing title when clicked', () => {
b69ab31310 expectIsNOTEditingTitle();
b69ab31311 clickToEditTitle();
b69ab31312 expectIsEditingTitle();
b69ab31313 });
b69ab31314
b69ab31315 it('starts editing description when clicked', () => {
b69ab31316 expectIsNOTEditingDescription();
b69ab31317 clickToEditDescription();
b69ab31318 expectIsEditingDescription();
b69ab31319 });
b69ab31320
b69ab31321 it('cancel button stops editing', () => {
b69ab31322 clickToEditTitle();
b69ab31323 clickToEditDescription();
b69ab31324 expectIsEditingTitle();
b69ab31325 expectIsEditingDescription();
b69ab31326
b69ab31327 const cancelButton: HTMLButtonElement | null = withinCommitInfo().queryByText('Cancel');
b69ab31328 expect(cancelButton).toBeInTheDocument();
b69ab31329
b69ab31330 act(() => {
b69ab31331 fireEvent.click(cancelButton!);
b69ab31332 });
b69ab31333
b69ab31334 expectIsNOTEditingTitle();
b69ab31335 expectIsNOTEditingDescription();
b69ab31336 });
b69ab31337
b69ab31338 it('amend button stops editing', async () => {
b69ab31339 act(() =>
b69ab31340 simulateUncommittedChangedFiles({
b69ab31341 value: [{path: 'src/file1.js', status: 'M'}],
b69ab31342 }),
b69ab31343 );
b69ab31344
b69ab31345 clickToEditTitle();
b69ab31346 clickToEditDescription();
b69ab31347 expectIsEditingTitle();
b69ab31348 expectIsEditingDescription();
b69ab31349
b69ab31350 const amendButton: HTMLButtonElement | null = within(
b69ab31351 screen.getByTestId('commit-info-actions-bar'),
b69ab31352 ).queryByText('Amend');
b69ab31353 expect(amendButton).toBeInTheDocument();
b69ab31354 expect(amendButton?.disabled).not.toEqual(true);
b69ab31355
b69ab31356 act(() => {
b69ab31357 fireEvent.click(amendButton!);
b69ab31358 });
b69ab31359
b69ab31360 await waitFor(() =>
b69ab31361 expectMessageSentToServer({
b69ab31362 type: 'runOperation',
b69ab31363 operation: expect.objectContaining({
b69ab31364 args: expect.arrayContaining(['amend']),
b69ab31365 }),
b69ab31366 }),
b69ab31367 );
b69ab31368
b69ab31369 expectIsNOTEditingTitle();
b69ab31370 expectIsNOTEditingDescription();
b69ab31371 });
b69ab31372
b69ab31373 it('resets edited fields when changing selected commit', () => {
b69ab31374 clickToEditTitle();
b69ab31375 clickToEditDescription();
b69ab31376 expectIsEditingTitle();
b69ab31377 expectIsEditingDescription();
b69ab31378
b69ab31379 clickToSelectCommit('a');
b69ab31380
b69ab31381 expectIsNOTEditingTitle();
b69ab31382 expectIsNOTEditingDescription();
b69ab31383 });
b69ab31384
b69ab31385 it('fields stay reset after switching commit, if there were no real changes made', () => {
b69ab31386 clickToEditTitle();
b69ab31387 clickToEditDescription();
b69ab31388 expectIsEditingTitle();
b69ab31389 expectIsEditingDescription();
b69ab31390
b69ab31391 clickToSelectCommit('a');
b69ab31392
b69ab31393 expectIsNOTEditingTitle();
b69ab31394 expectIsNOTEditingDescription();
b69ab31395
b69ab31396 clickToSelectCommit('b');
b69ab31397
b69ab31398 expectIsNOTEditingTitle();
b69ab31399 expectIsNOTEditingDescription();
b69ab31400 });
b69ab31401
b69ab31402 it('edited fields go back into editing state when returning to selected commit', () => {
b69ab31403 clickToEditTitle();
b69ab31404 clickToEditDescription();
b69ab31405 expectIsEditingTitle();
b69ab31406 expectIsEditingDescription();
b69ab31407
b69ab31408 {
b69ab31409 act(() => {
b69ab31410 userEvent.type(getTitleEditor(), ' hello new title');
b69ab31411 userEvent.type(getDescriptionEditor(), '\nhello new text');
b69ab31412 });
b69ab31413 }
b69ab31414
b69ab31415 clickToSelectCommit('a');
b69ab31416
b69ab31417 expectIsNOTEditingTitle();
b69ab31418 expectIsNOTEditingDescription();
b69ab31419
b69ab31420 clickToSelectCommit('b');
b69ab31421
b69ab31422 expectIsEditingTitle();
b69ab31423 expectIsEditingDescription();
b69ab31424
b69ab31425 {
b69ab31426 expect(getTitleEditor().value).toBe('Head Commit hello new title');
b69ab31427 expect(getDescriptionEditor().value).toEqual(
b69ab31428 expect.stringContaining('stacked commit\nhello new text'),
b69ab31429 );
b69ab31430 }
b69ab31431 });
b69ab31432
b69ab31433 it('cannot type newlines into title', () => {
b69ab31434 clickToEditTitle();
b69ab31435 expectIsEditingTitle();
b69ab31436
b69ab31437 act(() => {
b69ab31438 userEvent.type(getTitleEditor(), ' hello\nsomething\r\nhi');
b69ab31439 });
b69ab31440 expect(getTitleEditor().value).toBe('Head Commit hellosomethinghi');
b69ab31441 });
b69ab31442
b69ab31443 describe('focus', () => {
b69ab31444 it('focuses title when you start editing', async () => {
b69ab31445 clickToEditTitle();
b69ab31446
b69ab31447 await waitFor(() => {
b69ab31448 expect(getTitleEditor()).toHaveFocus();
b69ab31449 });
b69ab31450 });
b69ab31451
b69ab31452 it('focuses summary when you start editing', async () => {
b69ab31453 clickToEditDescription();
b69ab31454
b69ab31455 await waitFor(() => {
b69ab31456 expect(getDescriptionEditor()).toHaveFocus();
b69ab31457 });
b69ab31458 });
b69ab31459
b69ab31460 it('focuses topmost field when all fields start being edited', async () => {
b69ab31461 act(() => {
b69ab31462 simulateUncommittedChangedFiles({value: [{path: 'src/file1.js', status: 'M'}]});
b69ab31463 });
b69ab31464 // edit fields, then switch selected commit and switch back to edit both fields together
b69ab31465 fireEvent.click(screen.getByText('Amend as...', {exact: false}));
b69ab31466
b69ab31467 await waitFor(() => {
b69ab31468 expect(getTitleEditor()).toHaveFocus();
b69ab31469 expect(getDescriptionEditor()).not.toHaveFocus();
b69ab31470 });
b69ab31471 });
b69ab31472
b69ab31473 it('focuses topmost edited field when loading from saved state', async () => {
b69ab31474 act(() => {
b69ab31475 clickToEditTitle();
b69ab31476 clickToEditDescription();
b69ab31477 });
b69ab31478 {
b69ab31479 act(() => {
b69ab31480 userEvent.type(getTitleEditor(), ' hello new title');
b69ab31481 userEvent.type(getDescriptionEditor(), '\nhello new text');
b69ab31482 });
b69ab31483 }
b69ab31484
b69ab31485 clickToSelectCommit('a');
b69ab31486 clickToSelectCommit('b');
b69ab31487
b69ab31488 expectIsEditingTitle();
b69ab31489 expectIsEditingDescription();
b69ab31490
b69ab31491 await waitFor(() => {
b69ab31492 expect(getTitleEditor()).toHaveFocus();
b69ab31493 expect(getDescriptionEditor()).not.toHaveFocus();
b69ab31494 });
b69ab31495 });
b69ab31496 });
b69ab31497
b69ab31498 describe('head commit', () => {
b69ab31499 it('only has metaedit button on non-head commits', () => {
b69ab31500 {
b69ab31501 const preChangeAmendMessageButton = within(
b69ab31502 screen.getByTestId('commit-info-actions-bar'),
b69ab31503 ).queryByText('Amend Message');
b69ab31504 expect(preChangeAmendMessageButton).not.toBeInTheDocument();
b69ab31505 }
b69ab31506
b69ab31507 clickToSelectCommit('a');
b69ab31508
b69ab31509 const amendMessageButton = within(
b69ab31510 screen.getByTestId('commit-info-actions-bar'),
b69ab31511 ).queryByText('Amend Message');
b69ab31512 expect(amendMessageButton).toBeInTheDocument();
b69ab31513 });
b69ab31514
b69ab31515 it('has "You are here" on head commit', () => {
b69ab31516 expect(withinCommitInfo().queryByText('You are here')).toBeInTheDocument();
b69ab31517 });
b69ab31518
b69ab31519 it('does not have "You are here" on non-head commit', () => {
b69ab31520 clickToSelectCommit('a');
b69ab31521 expect(withinCommitInfo().queryByText('You are here')).not.toBeInTheDocument();
b69ab31522 });
b69ab31523
b69ab31524 it('does not have "You are here" in commit mode', () => {
b69ab31525 clickCommitMode();
b69ab31526 expect(withinCommitInfo().queryByText('You are here')).not.toBeInTheDocument();
b69ab31527 });
b69ab31528 });
b69ab31529
b69ab31530 describe('running commands', () => {
b69ab31531 describe('metaedit', () => {
b69ab31532 it('disables metaedit button if no fields edited', () => {
b69ab31533 clickToSelectCommit('a');
b69ab31534
b69ab31535 const amendMessageButton = within(
b69ab31536 screen.getByTestId('commit-info-actions-bar'),
b69ab31537 ).queryByText('Amend Message') as HTMLButtonElement;
b69ab31538 expect(amendMessageButton).toBeInTheDocument();
b69ab31539 expect(amendMessageButton!.disabled).toBe(true);
b69ab31540 });
b69ab31541
b69ab31542 it('enables metaedit button if fields are edited', () => {
b69ab31543 clickToSelectCommit('a');
b69ab31544
b69ab31545 act(() => {
b69ab31546 clickToEditTitle();
b69ab31547 clickToEditDescription();
b69ab31548 });
b69ab31549 });
b69ab31550
b69ab31551 it('runs metaedit', async () => {
b69ab31552 clickToSelectCommit('a');
b69ab31553
b69ab31554 act(() => {
b69ab31555 clickToEditTitle();
b69ab31556 clickToEditDescription();
b69ab31557 });
b69ab31558
b69ab31559 {
b69ab31560 act(() => {
b69ab31561 userEvent.type(getTitleEditor(), ' hello new title');
b69ab31562 userEvent.type(getDescriptionEditor(), '\nhello new text');
b69ab31563 });
b69ab31564 }
b69ab31565
b69ab31566 const amendMessageButton = within(
b69ab31567 screen.getByTestId('commit-info-actions-bar'),
b69ab31568 ).queryByText('Amend Message');
b69ab31569 act(() => {
b69ab31570 fireEvent.click(amendMessageButton!);
b69ab31571 });
b69ab31572 await waitFor(() =>
b69ab31573 expectMessageSentToServer({
b69ab31574 type: 'runOperation',
b69ab31575 operation: {
b69ab31576 args: [
b69ab31577 'metaedit',
b69ab31578 '--rev',
b69ab31579 succeedableRevset('a'),
b69ab31580 '--message',
b69ab31581 expect.stringMatching(
b69ab31582 /^My Commit hello new title\n+(Summary:\s+)?First commit in the stack\nhello new text/,
b69ab31583 ),
b69ab31584 ],
b69ab31585 id: expect.anything(),
b69ab31586 runner: CommandRunner.Sapling,
b69ab31587 trackEventName: 'AmendMessageOperation',
b69ab31588 },
b69ab31589 }),
b69ab31590 );
b69ab31591 });
b69ab31592
b69ab31593 it('disables metaedit button with spinner while running', async () => {
b69ab31594 clickToSelectCommit('a');
b69ab31595
b69ab31596 act(() => {
b69ab31597 clickToEditTitle();
b69ab31598 clickToEditDescription();
b69ab31599 });
b69ab31600 {
b69ab31601 act(() => {
b69ab31602 userEvent.type(getTitleEditor(), ' hello new title');
b69ab31603 userEvent.type(getDescriptionEditor(), '\nhello new text');
b69ab31604 });
b69ab31605 }
b69ab31606
b69ab31607 const amendMessageButton = within(
b69ab31608 screen.getByTestId('commit-info-actions-bar'),
b69ab31609 ).queryByText('Amend Message');
b69ab31610 act(() => {
b69ab31611 fireEvent.click(amendMessageButton!);
b69ab31612 });
b69ab31613
b69ab31614 await waitForWithTick(() => {
b69ab31615 expect(amendMessageButton).toBeDisabled();
b69ab31616 });
b69ab31617 });
b69ab31618 });
b69ab31619
b69ab31620 describe('amend', () => {
b69ab31621 it('runs amend with changed message', async () => {
b69ab31622 act(() =>
b69ab31623 simulateUncommittedChangedFiles({
b69ab31624 value: [{path: 'src/file1.js', status: 'M'}],
b69ab31625 }),
b69ab31626 );
b69ab31627
b69ab31628 act(() => {
b69ab31629 clickToEditTitle();
b69ab31630 clickToEditDescription();
b69ab31631 });
b69ab31632
b69ab31633 {
b69ab31634 act(() => {
b69ab31635 userEvent.type(getTitleEditor(), ' hello new title');
b69ab31636 userEvent.type(getDescriptionEditor(), '\nhello new text');
b69ab31637 });
b69ab31638 }
b69ab31639
b69ab31640 clickAmendButton();
b69ab31641
b69ab31642 await waitFor(() =>
b69ab31643 expectMessageSentToServer({
b69ab31644 type: 'runOperation',
b69ab31645 operation: {
b69ab31646 args: [
b69ab31647 {type: 'config', key: 'amend.autorestack', value: 'always'},
b69ab31648 'amend',
b69ab31649 '--addremove',
b69ab31650 '--message',
b69ab31651 expect.stringMatching(
b69ab31652 /^Head Commit hello new title\n+(Summary:\s+)?stacked commit\nhello new text/,
b69ab31653 ),
b69ab31654 ],
b69ab31655 id: expect.anything(),
b69ab31656 runner: CommandRunner.Sapling,
b69ab31657 trackEventName: 'AmendOperation',
b69ab31658 },
b69ab31659 }),
b69ab31660 );
b69ab31661 });
b69ab31662
b69ab31663 it('deselects head when running amend', async () => {
b69ab31664 act(() =>
b69ab31665 simulateUncommittedChangedFiles({
b69ab31666 value: [{path: 'src/file1.js', status: 'M'}],
b69ab31667 }),
b69ab31668 );
b69ab31669
b69ab31670 // even though 'b' is already shown when nothing is selected,
b69ab31671 // we want to use auto-selection after amending even if you previously selected something
b69ab31672 clickToSelectCommit('b');
b69ab31673
b69ab31674 clickToEditTitle();
b69ab31675
b69ab31676 {
b69ab31677 act(() => {
b69ab31678 userEvent.type(getTitleEditor(), ' hello');
b69ab31679 });
b69ab31680 }
b69ab31681
b69ab31682 clickAmendButton();
b69ab31683
b69ab31684 // no commit is selected anymore
b69ab31685 await waitFor(() =>
b69ab31686 expect(screen.queryByTestId('selected-commit')).not.toBeInTheDocument(),
b69ab31687 );
b69ab31688 });
b69ab31689
b69ab31690 it('does not deselect non-head commits after running amend', () => {
b69ab31691 act(() => {
b69ab31692 simulateUncommittedChangedFiles({
b69ab31693 value: [{path: 'src/file1.js', status: 'M'}],
b69ab31694 });
b69ab31695 });
b69ab31696
b69ab31697 clickToSelectCommit('a');
b69ab31698
b69ab31699 // we can't use amend button in the commit info because we're on some other commit, so use quick amend
b69ab31700 const quickAmendButton = screen.getByTestId('uncommitted-changes-quick-amend-button');
b69ab31701 act(() => {
b69ab31702 fireEvent.click(quickAmendButton);
b69ab31703 });
b69ab31704
b69ab31705 // commit remains selected
b69ab31706 expect(
b69ab31707 within(screen.getByTestId('commit-a')).queryByTestId('selected-commit'),
b69ab31708 ).toBeInTheDocument();
b69ab31709 });
b69ab31710
b69ab31711 it('disables amend button with spinner while running', async () => {
b69ab31712 act(() => {
b69ab31713 simulateUncommittedChangedFiles({
b69ab31714 value: [{path: 'src/file1.js', status: 'M'}],
b69ab31715 });
b69ab31716 });
b69ab31717
b69ab31718 clickAmendButton();
b69ab31719 await waitFor(() =>
b69ab31720 expectMessageSentToServer({
b69ab31721 type: 'runOperation',
b69ab31722 operation: expect.objectContaining({
b69ab31723 args: expect.arrayContaining(['amend']),
b69ab31724 }),
b69ab31725 }),
b69ab31726 );
b69ab31727
b69ab31728 act(() => {
b69ab31729 simulateUncommittedChangedFiles({
b69ab31730 value: [{path: 'src/file2.js', status: 'M'}],
b69ab31731 });
b69ab31732 });
b69ab31733
b69ab31734 const amendMessageButton = within(
b69ab31735 screen.getByTestId('commit-info-actions-bar'),
b69ab31736 ).queryByText('Amend');
b69ab31737 act(() => {
b69ab31738 fireEvent.click(amendMessageButton!);
b69ab31739 });
b69ab31740
b69ab31741 expect(amendMessageButton).toBeDisabled();
b69ab31742 });
b69ab31743
b69ab31744 it('shows amend message instead of amend when there are only message changes', () => {
b69ab31745 act(() => {
b69ab31746 simulateUncommittedChangedFiles({
b69ab31747 value: [{path: 'src/file1.js', status: 'M'}],
b69ab31748 });
b69ab31749 });
b69ab31750
b69ab31751 expect(
b69ab31752 within(screen.getByTestId('commit-info-actions-bar')).queryByText('Amend'),
b69ab31753 ).toBeInTheDocument();
b69ab31754
b69ab31755 act(() => {
b69ab31756 simulateUncommittedChangedFiles({
b69ab31757 value: [],
b69ab31758 });
b69ab31759 });
b69ab31760
b69ab31761 expect(
b69ab31762 within(screen.getByTestId('commit-info-actions-bar')).queryByText('Amend'),
b69ab31763 ).toBeInTheDocument();
b69ab31764
b69ab31765 act(() => {
b69ab31766 clickToEditTitle();
b69ab31767 clickToEditDescription();
b69ab31768 });
b69ab31769
b69ab31770 // no uncommitted changes, and message is being changed
b69ab31771 expect(
b69ab31772 within(screen.getByTestId('commit-info-actions-bar')).queryByText('Amend Message'),
b69ab31773 ).toBeInTheDocument();
b69ab31774 });
b69ab31775 });
b69ab31776 });
b69ab31777
b69ab31778 describe('commit mode', () => {
b69ab31779 it('has commit mode selector on head commit', () => {
b69ab31780 expect(
b69ab31781 within(screen.getByTestId('commit-info-toolbar-top')).getByText('Amend'),
b69ab31782 ).toBeInTheDocument();
b69ab31783 expect(
b69ab31784 within(screen.getByTestId('commit-info-toolbar-top')).getByText('Commit'),
b69ab31785 ).toBeInTheDocument();
b69ab31786 });
b69ab31787
b69ab31788 it('does not have commit mode selector on non-head commits', () => {
b69ab31789 clickToSelectCommit('a');
b69ab31790 // toolbar won't appear at all on non-head commits right now
b69ab31791 expect(screen.queryByTestId('commit-info-toolbar-top')).not.toBeInTheDocument();
b69ab31792 });
b69ab31793
b69ab31794 it('clicking commit mode starts editing both fields', () => {
b69ab31795 clickCommitMode();
b69ab31796
b69ab31797 expectIsEditingTitle();
b69ab31798 expectIsEditingDescription();
b69ab31799 });
b69ab31800
b69ab31801 it('commit mode message is saved separately', () => {
b69ab31802 clickCommitMode();
b69ab31803
b69ab31804 expectIsEditingTitle();
b69ab31805 expectIsEditingDescription();
b69ab31806
b69ab31807 act(() => {
b69ab31808 userEvent.type(getTitleEditor(), 'new commit title');
b69ab31809 userEvent.type(getDescriptionEditor(), 'my description');
b69ab31810 });
b69ab31811
b69ab31812 clickToSelectCommit('a');
b69ab31813 clickToSelectCommit('b');
b69ab31814
b69ab31815 expectIsEditingTitle();
b69ab31816 expectIsEditingDescription();
b69ab31817
b69ab31818 expect(getTitleEditor().value).toBe('new commit title');
b69ab31819 expect(getDescriptionEditor().value).toBe('my description');
b69ab31820 });
b69ab31821
b69ab31822 it('focuses title when opening commit mode', async () => {
b69ab31823 clickCommitMode();
b69ab31824
b69ab31825 await waitFor(() => {
b69ab31826 expect(getTitleEditor()).toHaveFocus();
b69ab31827 expect(getDescriptionEditor()).not.toHaveFocus();
b69ab31828 });
b69ab31829 });
b69ab31830
b69ab31831 it("disables commit button if there's no changed files", () => {
b69ab31832 clickCommitMode();
b69ab31833
b69ab31834 const commitButton = within(screen.getByTestId('commit-info-actions-bar')).queryByText(
b69ab31835 'Commit',
b69ab31836 ) as HTMLButtonElement;
b69ab31837 expect(commitButton).toBeInTheDocument();
b69ab31838 expect(commitButton!.disabled).toBe(true);
b69ab31839 });
b69ab31840
b69ab31841 it('does not disable commit button if there are changed files', () => {
b69ab31842 act(() => {
b69ab31843 simulateUncommittedChangedFiles({
b69ab31844 value: [
b69ab31845 {path: 'src/file1.js', status: 'M'},
b69ab31846 {path: 'src/file2.js', status: 'M'},
b69ab31847 ],
b69ab31848 });
b69ab31849 });
b69ab31850
b69ab31851 clickCommitMode();
b69ab31852
b69ab31853 const commitButton = within(screen.getByTestId('commit-info-actions-bar')).queryByText(
b69ab31854 'Commit',
b69ab31855 ) as HTMLButtonElement;
b69ab31856 expect(commitButton).toBeInTheDocument();
b69ab31857 expect(commitButton!.disabled).not.toBe(true);
b69ab31858 });
b69ab31859
b69ab31860 it('runs commit with message', async () => {
b69ab31861 act(() => {
b69ab31862 simulateUncommittedChangedFiles({
b69ab31863 value: [
b69ab31864 {path: 'src/file1.js', status: 'M'},
b69ab31865 {path: 'src/file2.js', status: 'M'},
b69ab31866 ],
b69ab31867 });
b69ab31868 });
b69ab31869
b69ab31870 clickCommitMode();
b69ab31871
b69ab31872 act(() => {
b69ab31873 userEvent.type(getTitleEditor(), 'new commit title');
b69ab31874 userEvent.type(getDescriptionEditor(), 'my description');
b69ab31875 });
b69ab31876
b69ab31877 clickCommitButton();
b69ab31878
b69ab31879 await waitFor(() =>
b69ab31880 expectMessageSentToServer({
b69ab31881 type: 'runOperation',
b69ab31882 operation: {
b69ab31883 args: [
b69ab31884 'commit',
b69ab31885 '--addremove',
b69ab31886 '--message',
b69ab31887 expect.stringMatching(/^new commit title\n+(Summary:\s+)?my description/),
b69ab31888 ],
b69ab31889 id: expect.anything(),
b69ab31890 runner: CommandRunner.Sapling,
b69ab31891 trackEventName: 'CommitOperation',
b69ab31892 },
b69ab31893 }),
b69ab31894 );
b69ab31895 });
b69ab31896
b69ab31897 it('resets to amend mode after committing', async () => {
b69ab31898 act(() => {
b69ab31899 simulateUncommittedChangedFiles({
b69ab31900 value: [
b69ab31901 {path: 'src/file1.js', status: 'M'},
b69ab31902 {path: 'src/file2.js', status: 'M'},
b69ab31903 ],
b69ab31904 });
b69ab31905 });
b69ab31906
b69ab31907 clickCommitMode();
b69ab31908
b69ab31909 act(() => {
b69ab31910 userEvent.type(getTitleEditor(), 'new commit title');
b69ab31911 userEvent.type(getDescriptionEditor(), 'my description');
b69ab31912 });
b69ab31913
b69ab31914 clickCommitButton();
b69ab31915
b69ab31916 await waitFor(() =>
b69ab31917 expectMessageSentToServer({
b69ab31918 type: 'runOperation',
b69ab31919 operation: expect.objectContaining({
b69ab31920 args: expect.arrayContaining(['commit']),
b69ab31921 }),
b69ab31922 }),
b69ab31923 );
b69ab31924
b69ab31925 const commitButtonAfter = within(
b69ab31926 screen.getByTestId('commit-info-actions-bar'),
b69ab31927 ).queryByText('Commit');
b69ab31928 expect(commitButtonAfter).not.toBeInTheDocument();
b69ab31929 });
b69ab31930
b69ab31931 it('does not have cancel button', () => {
b69ab31932 clickCommitMode();
b69ab31933
b69ab31934 const cancelButton: HTMLButtonElement | null = withinCommitInfo().queryByText('Cancel');
b69ab31935 expect(cancelButton).not.toBeInTheDocument();
b69ab31936 });
b69ab31937 });
b69ab31938
b69ab31939 describe('edited messages indicator', () => {
b69ab31940 it('does not show edited message indicator when fields are not actually changed', () => {
b69ab31941 act(() => {
b69ab31942 clickToEditTitle();
b69ab31943 clickToEditDescription();
b69ab31944 });
b69ab31945 expectIsEditingTitle();
b69ab31946 expectIsEditingDescription();
b69ab31947
b69ab31948 expect(screen.queryByTestId('unsaved-message-indicator')).not.toBeInTheDocument();
b69ab31949
b69ab31950 {
b69ab31951 act(() => {
b69ab31952 // type something and delete it
b69ab31953 userEvent.type(getTitleEditor(), 'Q{Backspace}');
b69ab31954 userEvent.type(getDescriptionEditor(), 'Q{Backspace}');
b69ab31955 });
b69ab31956 }
b69ab31957
b69ab31958 expect(screen.queryByTestId('unsaved-message-indicator')).not.toBeInTheDocument();
b69ab31959 });
b69ab31960
b69ab31961 it('shows edited message indicator when title changed', () => {
b69ab31962 act(() => {
b69ab31963 clickToEditTitle();
b69ab31964 clickToEditDescription();
b69ab31965 });
b69ab31966
b69ab31967 expect(screen.queryByTestId('unsaved-message-indicator')).not.toBeInTheDocument();
b69ab31968
b69ab31969 {
b69ab31970 act(() => {
b69ab31971 userEvent.type(getTitleEditor(), 'Q');
b69ab31972 });
b69ab31973 }
b69ab31974
b69ab31975 expect(screen.queryByTestId('unsaved-message-indicator')).toBeInTheDocument();
b69ab31976 expect(
b69ab31977 within(screen.queryByTestId('commit-b')!).queryByTestId('unsaved-message-indicator'),
b69ab31978 ).toBeInTheDocument();
b69ab31979 });
b69ab31980
b69ab31981 it('shows edited message indicator when description changed', () => {
b69ab31982 act(() => {
b69ab31983 clickToEditTitle();
b69ab31984 clickToEditDescription();
b69ab31985 });
b69ab31986
b69ab31987 expect(screen.queryByTestId('unsaved-message-indicator')).not.toBeInTheDocument();
b69ab31988
b69ab31989 {
b69ab31990 act(() => {
b69ab31991 userEvent.type(getDescriptionEditor(), 'Q');
b69ab31992 });
b69ab31993 }
b69ab31994
b69ab31995 expect(screen.queryByTestId('unsaved-message-indicator')).toBeInTheDocument();
b69ab31996 expect(
b69ab31997 within(screen.queryByTestId('commit-b')!).queryByTestId('unsaved-message-indicator'),
b69ab31998 ).toBeInTheDocument();
b69ab31999 });
b69ab311000
b69ab311001 it('appears for other commits', () => {
b69ab311002 clickToSelectCommit('a');
b69ab311003
b69ab311004 act(() => {
b69ab311005 clickToEditTitle();
b69ab311006 clickToEditDescription();
b69ab311007 });
b69ab311008
b69ab311009 expect(screen.queryByTestId('unsaved-message-indicator')).not.toBeInTheDocument();
b69ab311010
b69ab311011 {
b69ab311012 act(() => {
b69ab311013 userEvent.type(getTitleEditor(), 'Q');
b69ab311014 userEvent.type(getDescriptionEditor(), 'Q');
b69ab311015 });
b69ab311016 }
b69ab311017
b69ab311018 expect(screen.queryByTestId('unsaved-message-indicator')).toBeInTheDocument();
b69ab311019 expect(
b69ab311020 within(screen.queryByTestId('commit-a')!).queryByTestId('unsaved-message-indicator'),
b69ab311021 ).toBeInTheDocument();
b69ab311022 });
b69ab311023
b69ab311024 it('commit mode does not cause indicator', () => {
b69ab311025 clickCommitMode();
b69ab311026
b69ab311027 {
b69ab311028 act(() => {
b69ab311029 userEvent.type(getTitleEditor(), 'Q');
b69ab311030 userEvent.type(getDescriptionEditor(), 'Q');
b69ab311031 });
b69ab311032 }
b69ab311033 expect(screen.queryByTestId('unsaved-message-indicator')).not.toBeInTheDocument();
b69ab311034 });
b69ab311035 });
b69ab311036
b69ab311037 describe('commit message template', () => {
b69ab311038 it('requests commit template when opening commit form', () => {
b69ab311039 clickCommitMode();
b69ab311040 expectMessageSentToServer({type: 'fetchCommitMessageTemplate'});
b69ab311041 });
b69ab311042
b69ab311043 it('loads template sent by server', () => {
b69ab311044 clickCommitMode();
b69ab311045 act(() => {
b69ab311046 simulateMessageFromServer({
b69ab311047 type: 'fetchedCommitMessageTemplate',
b69ab311048 template: '[isl]\nSummary: Hello\nTest Plan:\n',
b69ab311049 });
b69ab311050 });
b69ab311051
b69ab311052 expect(getTitleEditor().value).toBe('[isl]');
b69ab311053 expect(getDescriptionEditor().value).toEqual(expect.stringMatching(/(Summary: )?Hello/));
b69ab311054 });
b69ab311055
b69ab311056 it('only asynchronously overwrites default commit fields', () => {
b69ab311057 clickCommitMode();
b69ab311058
b69ab311059 // type something in fields...
b69ab311060 {
b69ab311061 act(() => {
b69ab311062 userEvent.type(getTitleEditor(), 'Q');
b69ab311063 userEvent.type(getDescriptionEditor(), 'W');
b69ab311064 });
b69ab311065 }
b69ab311066
b69ab311067 // template arrives from server later
b69ab311068 act(() => {
b69ab311069 simulateMessageFromServer({
b69ab311070 type: 'fetchedCommitMessageTemplate',
b69ab311071 template: '[isl]\nSummary:\nTest Plan:\n',
b69ab311072 });
b69ab311073 });
b69ab311074
b69ab311075 // template shouldn't have overwritten fields since they're non-default now
b69ab311076 expect(getTitleEditor().value).toBe('Q');
b69ab311077 expect(getDescriptionEditor().value).toBe('W');
b69ab311078 });
b69ab311079 });
b69ab311080
b69ab311081 describe('discarding message', () => {
b69ab311082 it('confirms cancel button if you have made changes to the title', async () => {
b69ab311083 clickToEditTitle();
b69ab311084 const confirmSpy = jest
b69ab311085 .spyOn(platform, 'confirm')
b69ab311086 .mockImplementation(() => Promise.resolve(true));
b69ab311087
b69ab311088 act(() => {
b69ab311089 userEvent.type(getTitleEditor(), 'Q');
b69ab311090 });
b69ab311091
b69ab311092 clickCancel();
b69ab311093
b69ab311094 await waitFor(() => {
b69ab311095 expectIsNOTEditingTitle();
b69ab311096 expectIsNOTEditingDescription();
b69ab311097 });
b69ab311098 expect(confirmSpy).toHaveBeenCalled();
b69ab311099 });
b69ab311100
b69ab311101 it('confirms cancel button if you have made changes to the description', async () => {
b69ab311102 clickToEditDescription();
b69ab311103 const confirmSpy = jest
b69ab311104 .spyOn(platform, 'confirm')
b69ab311105 .mockImplementation(() => Promise.resolve(true));
b69ab311106
b69ab311107 act(() => {
b69ab311108 userEvent.type(getDescriptionEditor(), 'W');
b69ab311109 });
b69ab311110
b69ab311111 clickCancel();
b69ab311112
b69ab311113 await waitFor(() => {
b69ab311114 expectIsNOTEditingTitle();
b69ab311115 expectIsNOTEditingDescription();
b69ab311116 });
b69ab311117 expect(confirmSpy).toHaveBeenCalled();
b69ab311118 });
b69ab311119
b69ab311120 it('does not cancel if you do not confirm', async () => {
b69ab311121 act(() => {
b69ab311122 clickToEditTitle();
b69ab311123 clickToEditDescription();
b69ab311124 });
b69ab311125 const confirmSpy = jest
b69ab311126 .spyOn(platform, 'confirm')
b69ab311127 .mockImplementation(() => Promise.resolve(false));
b69ab311128
b69ab311129 act(() => {
b69ab311130 userEvent.type(getTitleEditor(), 'Q');
b69ab311131 userEvent.type(getDescriptionEditor(), 'W');
b69ab311132 });
b69ab311133
b69ab311134 clickCancel();
b69ab311135
b69ab311136 await waitFor(() => {
b69ab311137 expectIsEditingTitle();
b69ab311138 expectIsEditingDescription();
b69ab311139
b69ab311140 expect(getTitleEditor().value).toBe('Head CommitQ');
b69ab311141 expect(getDescriptionEditor().value).toEqual(
b69ab311142 expect.stringContaining('stacked commitW'),
b69ab311143 );
b69ab311144 });
b69ab311145 expect(confirmSpy).toHaveBeenCalled();
b69ab311146 });
b69ab311147
b69ab311148 it('does not confirm when clearing for amend', async () => {
b69ab311149 act(() =>
b69ab311150 simulateUncommittedChangedFiles({
b69ab311151 value: [{path: 'src/file1.js', status: 'M'}],
b69ab311152 }),
b69ab311153 );
b69ab311154
b69ab311155 clickToEditDescription();
b69ab311156 const confirmSpy = jest.spyOn(platform, 'confirm');
b69ab311157
b69ab311158 act(() => {
b69ab311159 userEvent.type(getDescriptionEditor(), 'W');
b69ab311160 });
b69ab311161
b69ab311162 clickAmendButton();
b69ab311163
b69ab311164 await waitForWithTick(() => {
b69ab311165 expectIsNOTEditingTitle();
b69ab311166 expectIsNOTEditingDescription();
b69ab311167 expect(confirmSpy).not.toHaveBeenCalled();
b69ab311168 });
b69ab311169 });
b69ab311170 });
b69ab311171
b69ab311172 describe('optimistic state', () => {
b69ab311173 it('takes previews into account when rendering head', async () => {
b69ab311174 await CommitTreeListTestUtils.clickGoto('a');
b69ab311175 // while optimistic state happening...
b69ab311176 // show new commit in commit info without clicking it (because head is auto-selected)
b69ab311177 expect(withinCommitInfo().queryByText('My Commit')).toBeInTheDocument();
b69ab311178 expect(withinCommitInfo().queryByText('You are here')).toBeInTheDocument();
b69ab311179 });
b69ab311180
b69ab311181 it('shows new head when running goto', async () => {
b69ab311182 clickToSelectCommit('b'); // explicitly select
b69ab311183 await CommitTreeListTestUtils.clickGoto('a');
b69ab311184
b69ab311185 expect(withinCommitInfo().queryByText('My Commit')).toBeInTheDocument();
b69ab311186 expect(withinCommitInfo().queryByText('You are here')).toBeInTheDocument();
b69ab311187 });
b69ab311188
b69ab311189 it('renders metaedit operation smoothly', async () => {
b69ab311190 clickToSelectCommit('a');
b69ab311191
b69ab311192 act(() => {
b69ab311193 clickToEditTitle();
b69ab311194 clickToEditDescription();
b69ab311195 });
b69ab311196 act(() => {
b69ab311197 userEvent.type(getTitleEditor(), ' with change!');
b69ab311198 userEvent.type(getDescriptionEditor(), '\nmore stuff!');
b69ab311199 });
b69ab311200
b69ab311201 const amendMessageButton = within(
b69ab311202 screen.getByTestId('commit-info-actions-bar'),
b69ab311203 ).queryByText('Amend Message');
b69ab311204 act(() => {
b69ab311205 fireEvent.click(amendMessageButton!);
b69ab311206 });
b69ab311207
b69ab311208 await waitFor(() => {
b69ab311209 expectIsNOTEditingTitle();
b69ab311210 expectIsNOTEditingDescription();
b69ab311211
b69ab311212 expect(withinCommitInfo().getByText('My Commit with change!')).toBeInTheDocument();
b69ab311213 expect(
b69ab311214 withinCommitInfo().getByText(/First commit in the stack\nmore stuff!/, {
b69ab311215 collapseWhitespace: false,
b69ab311216 }),
b69ab311217 ).toBeInTheDocument();
b69ab311218 });
b69ab311219 });
b69ab311220
b69ab311221 it('renders commit operation smoothly', async () => {
b69ab311222 act(() => {
b69ab311223 simulateUncommittedChangedFiles({
b69ab311224 value: [{path: 'src/file1.js', status: 'M'}],
b69ab311225 });
b69ab311226 });
b69ab311227
b69ab311228 clickCommitMode();
b69ab311229 act(() => {
b69ab311230 userEvent.type(getTitleEditor(), 'New Commit');
b69ab311231 userEvent.type(getDescriptionEditor(), 'Message!');
b69ab311232 });
b69ab311233
b69ab311234 clickCommitButton();
b69ab311235
b69ab311236 // optimistic state should now be rendered, so we show a fake commit with the new title,
b69ab311237 // but not in editing mode anymore
b69ab311238
b69ab311239 await waitFor(() => {
b69ab311240 expectIsNOTEditingTitle();
b69ab311241 expectIsNOTEditingDescription();
b69ab311242
b69ab311243 expect(withinCommitInfo().queryByText('New Commit')).toBeInTheDocument();
b69ab311244 expect(withinCommitInfo().queryByText(/Message!/)).toBeInTheDocument();
b69ab311245 expect(withinCommitInfo().queryByText('You are here')).toBeInTheDocument();
b69ab311246 });
b69ab311247
b69ab311248 // finish commit operation with hg log
b69ab311249 act(() => {
b69ab311250 simulateCommits({
b69ab311251 value: [
b69ab311252 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab311253 COMMIT('a', 'My Commit', '1'),
b69ab311254 COMMIT('b', 'Head Commit', 'a'),
b69ab311255 COMMIT('c', 'New Commit', 'b', {
b69ab311256 isDot: true,
b69ab311257 description: 'Summary: Message!',
b69ab311258 }),
b69ab311259 ],
b69ab311260 });
b69ab311261 });
b69ab311262 expect(withinCommitInfo().queryByText('New Commit')).toBeInTheDocument();
b69ab311263 expect(withinCommitInfo().getByText(/Message!/)).toBeInTheDocument();
b69ab311264 expect(withinCommitInfo().queryByText('You are here')).toBeInTheDocument();
b69ab311265 });
b69ab311266
b69ab311267 it('doesnt let you edit on optimistic commit', async () => {
b69ab311268 act(() => {
b69ab311269 simulateUncommittedChangedFiles({
b69ab311270 value: [{path: 'src/file1.js', status: 'M'}],
b69ab311271 });
b69ab311272 });
b69ab311273
b69ab311274 clickCommitMode();
b69ab311275 act(() => {
b69ab311276 userEvent.type(getTitleEditor(), 'New Commit');
b69ab311277 userEvent.type(getDescriptionEditor(), 'Message!');
b69ab311278 });
b69ab311279 clickCommitButton();
b69ab311280
b69ab311281 await waitFor(() =>
b69ab311282 expectMessageSentToServer({
b69ab311283 type: 'runOperation',
b69ab311284 operation: expect.objectContaining({
b69ab311285 args: expect.arrayContaining(['commit']),
b69ab311286 }),
b69ab311287 }),
b69ab311288 );
b69ab311289
b69ab311290 clickToEditTitle();
b69ab311291 clickToEditDescription();
b69ab311292 // cannot click to edit optimistic commit
b69ab311293 expectIsNOTEditingTitle();
b69ab311294 expectIsNOTEditingDescription();
b69ab311295
b69ab311296 // finish commit operation with hg log
b69ab311297 act(() => {
b69ab311298 simulateCommits({
b69ab311299 value: [
b69ab311300 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab311301 COMMIT('a', 'My Commit', '1'),
b69ab311302 COMMIT('b', 'Head Commit', 'a'),
b69ab311303 COMMIT('c', 'New Commit', 'b', {isDot: true, description: 'Summary: Message!'}),
b69ab311304 ],
b69ab311305 });
b69ab311306 });
b69ab311307
b69ab311308 act(() => {
b69ab311309 clickToEditTitle();
b69ab311310 clickToEditDescription();
b69ab311311 });
b69ab311312 // now you can edit just fine
b69ab311313 expectIsEditingTitle();
b69ab311314 expectIsEditingDescription();
b69ab311315 });
b69ab311316
b69ab311317 it('renders amend operation smoothly', async () => {
b69ab311318 act(() =>
b69ab311319 simulateUncommittedChangedFiles({
b69ab311320 value: [{path: 'src/file1.js', status: 'M'}],
b69ab311321 }),
b69ab311322 );
b69ab311323
b69ab311324 act(() => {
b69ab311325 clickToEditTitle();
b69ab311326 clickToEditDescription();
b69ab311327 });
b69ab311328 act(() => {
b69ab311329 userEvent.type(getTitleEditor(), ' Hey');
b69ab311330 userEvent.type(getDescriptionEditor(), '\nHello');
b69ab311331 });
b69ab311332
b69ab311333 clickAmendButton();
b69ab311334
b69ab311335 // optimistic state should now be rendered, so we update the head commit
b69ab311336 // but not in editing mode anymore
b69ab311337
b69ab311338 await waitFor(() => {
b69ab311339 expectIsNOTEditingTitle();
b69ab311340 expectIsNOTEditingDescription();
b69ab311341
b69ab311342 expect(withinCommitInfo().getByText('Head Commit Hey')).toBeInTheDocument();
b69ab311343 expect(
b69ab311344 withinCommitInfo().getByText(/stacked commit\nHello/, {
b69ab311345 collapseWhitespace: false,
b69ab311346 }),
b69ab311347 ).toBeInTheDocument();
b69ab311348 expect(withinCommitInfo().getByText('You are here')).toBeInTheDocument();
b69ab311349 });
b69ab311350
b69ab311351 // finish amend operation with hg log
b69ab311352 act(() => {
b69ab311353 simulateCommits({
b69ab311354 value: [
b69ab311355 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab311356 COMMIT('a', 'My Commit', '1'),
b69ab311357 COMMIT('b2', 'Head Commit Hey', 'a', {
b69ab311358 isDot: true,
b69ab311359 description: 'Summary: stacked commit\nHello',
b69ab311360 }),
b69ab311361 ],
b69ab311362 });
b69ab311363 });
b69ab311364 expect(withinCommitInfo().getByText('Head Commit Hey')).toBeInTheDocument();
b69ab311365 expect(
b69ab311366 withinCommitInfo().getByText(/stacked commit\nHello/, {
b69ab311367 collapseWhitespace: false,
b69ab311368 }),
b69ab311369 ).toBeInTheDocument();
b69ab311370 expect(withinCommitInfo().getByText('You are here')).toBeInTheDocument();
b69ab311371 });
b69ab311372 });
b69ab311373
b69ab311374 describe('Opening form in edit mode from uncommitted changes', () => {
b69ab311375 beforeEach(() => {
b69ab311376 act(() => {
b69ab311377 simulateUncommittedChangedFiles({
b69ab311378 value: [{path: 'src/file1.js', status: 'M'}],
b69ab311379 });
b69ab311380 });
b69ab311381 });
b69ab311382
b69ab311383 const clickAmendAs = async () => {
b69ab311384 const amendAsButton = screen.getByText('Amend as...');
b69ab311385 act(() => {
b69ab311386 fireEvent.click(amendAsButton!);
b69ab311387 });
b69ab311388
b69ab311389 await waitFor(() => {
b69ab311390 expectIsEditingTitle();
b69ab311391 expectIsEditingDescription();
b69ab311392 });
b69ab311393 };
b69ab311394 const clickCommitAs = () => {
b69ab311395 const commitAsButton = screen.getByText('Commit as...');
b69ab311396 act(() => {
b69ab311397 fireEvent.click(commitAsButton!);
b69ab311398 });
b69ab311399 };
b69ab311400
b69ab311401 it('Opens form if closed', async () => {
b69ab311402 act(() => {
b69ab311403 closeCommitInfoSidebar();
b69ab311404 });
b69ab311405
b69ab311406 await clickAmendAs();
b69ab311407
b69ab311408 expect(screen.getByTestId('commit-info-view')).toBeInTheDocument();
b69ab311409 });
b69ab311410
b69ab311411 it('Deselects so head commit is shown', async () => {
b69ab311412 clickToSelectCommit('a');
b69ab311413 await clickAmendAs();
b69ab311414
b69ab311415 await waitFor(() => {
b69ab311416 // no commit is selected anymore
b69ab311417 expect(screen.queryByTestId('selected-commit')).not.toBeInTheDocument();
b69ab311418 expect(withinCommitInfo().queryByText('Head Commit')).toBeInTheDocument();
b69ab311419 });
b69ab311420 });
b69ab311421
b69ab311422 describe('Amend as...', () => {
b69ab311423 it('Opens form in amend mode', async () => {
b69ab311424 clickCommitMode();
b69ab311425 await clickAmendAs();
b69ab311426
b69ab311427 const amendButton: HTMLButtonElement | null = within(
b69ab311428 screen.getByTestId('commit-info-actions-bar'),
b69ab311429 ).queryByText('Amend');
b69ab311430 expect(amendButton).toBeInTheDocument();
b69ab311431 });
b69ab311432
b69ab311433 it('starts editing fields', async () => {
b69ab311434 clickCommitMode();
b69ab311435 await clickAmendAs();
b69ab311436
b69ab311437 await waitFor(() => {
b69ab311438 expectIsEditingTitle();
b69ab311439 expectIsEditingDescription();
b69ab311440 });
b69ab311441 });
b69ab311442
b69ab311443 it('focuses fields', async () => {
b69ab311444 await clickAmendAs();
b69ab311445
b69ab311446 await waitFor(() => {
b69ab311447 expect(getTitleEditor()).toHaveFocus();
b69ab311448 });
b69ab311449 });
b69ab311450 });
b69ab311451
b69ab311452 describe('Commit as...', () => {
b69ab311453 it('Opens form in commit mode', () => {
b69ab311454 clickCommitAs();
b69ab311455
b69ab311456 const commitButton: HTMLButtonElement | null = within(
b69ab311457 screen.getByTestId('commit-info-actions-bar'),
b69ab311458 ).queryByText('Commit');
b69ab311459 expect(commitButton).toBeInTheDocument();
b69ab311460 });
b69ab311461
b69ab311462 it('focuses fields', async () => {
b69ab311463 clickCommitAs();
b69ab311464
b69ab311465 await waitFor(() => {
b69ab311466 expect(getTitleEditor()).toHaveFocus();
b69ab311467 });
b69ab311468 });
b69ab311469
b69ab311470 it('focuses fields even if amend fields already being edited', async () => {
b69ab311471 await clickAmendAs();
b69ab311472
b69ab311473 await waitFor(() => {
b69ab311474 expect(getTitleEditor()).toHaveFocus();
b69ab311475 });
b69ab311476 expect(getTitleEditor().value).toEqual('Head Commit');
b69ab311477
b69ab311478 act(() => {
b69ab311479 // explicitly blur title so "commit as" really has to focus it again
b69ab311480 getTitleEditor().blur();
b69ab311481 });
b69ab311482
b69ab311483 clickCommitAs();
b69ab311484
b69ab311485 await waitFor(() => {
b69ab311486 expect(getTitleEditor().value).toEqual('');
b69ab311487 });
b69ab311488 expect(getTitleEditor()).toHaveFocus();
b69ab311489 });
b69ab311490
b69ab311491 it('copies commit title from quick commit form', () => {
b69ab311492 const title = screen.getByTestId('quick-commit-title');
b69ab311493 act(() => {
b69ab311494 userEvent.type(title, 'Hello, world!');
b69ab311495 });
b69ab311496 clickCommitAs();
b69ab311497
b69ab311498 expect((screen.getByTestId('quick-commit-title') as HTMLInputElement).value).toEqual(
b69ab311499 '',
b69ab311500 );
b69ab311501 expect(getTitleEditor().value).toBe('Hello, world!');
b69ab311502 });
b69ab311503 });
b69ab311504 });
b69ab311505 });
b69ab311506
b69ab311507 function expectAmendDisabled() {
b69ab311508 it('does not allow submitting', () => {
b69ab311509 expect(withinCommitInfo().queryByText('Submit')).not.toBeInTheDocument();
b69ab311510 });
b69ab311511
b69ab311512 it('does not show changes to amend', () => {
b69ab311513 expect(withinCommitInfo().queryByText('Changes to Amend')).not.toBeInTheDocument();
b69ab311514 });
b69ab311515
b69ab311516 it('does not allow clicking to edit fields', () => {
b69ab311517 expectIsNOTEditingTitle();
b69ab311518 expectIsNOTEditingDescription();
b69ab311519
b69ab311520 clickToEditTitle();
b69ab311521 clickToEditDescription();
b69ab311522
b69ab311523 expectIsNOTEditingTitle();
b69ab311524 expectIsNOTEditingDescription();
b69ab311525 });
b69ab311526 }
b69ab311527
b69ab311528 describe('Public commits in amend mode', () => {
b69ab311529 beforeEach(() => {
b69ab311530 act(() => {
b69ab311531 simulateCommits({
b69ab311532 value: [
b69ab311533 COMMIT('1', 'some public base', '0', {phase: 'public', isDot: true}),
b69ab311534 COMMIT('a', 'My Commit', '1'),
b69ab311535 COMMIT('b', 'Head Commit', 'a'),
b69ab311536 ],
b69ab311537 });
b69ab311538 });
b69ab311539 });
b69ab311540
b69ab311541 it('shows public label', () => {
b69ab311542 expect(withinCommitInfo().getByText('Public')).toBeInTheDocument();
b69ab311543 });
b69ab311544
b69ab311545 expectAmendDisabled();
b69ab311546 });
b69ab311547
b69ab311548 describe('Obsoleted commits in amend mode', () => {
b69ab311549 beforeEach(() => {
b69ab311550 act(() => {
b69ab311551 simulateCommits({
b69ab311552 value: [
b69ab311553 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab311554 COMMIT('a', 'My Commit V1', '1', {
b69ab311555 successorInfo: {hash: 'b', type: 'amend'},
b69ab311556 isDot: true,
b69ab311557 }),
b69ab311558 COMMIT('b', 'Head Commit', '1'),
b69ab311559 ],
b69ab311560 });
b69ab311561 });
b69ab311562 });
b69ab311563
b69ab311564 expectAmendDisabled();
b69ab311565 });
b69ab311566 });
b69ab311567});