addons/isl/src/__tests__/operations.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 * as utils from 'shared/utils';
b69ab3110import App from '../App';
b69ab3111import {Internal} from '../Internal';
b69ab3112import {tracker} from '../analytics';
b69ab3113import {readAtom} from '../jotaiUtils';
b69ab3114import {operationList} from '../operationsState';
b69ab3115import {mostRecentSubscriptionIds} from '../serverAPIState';
b69ab3116import {CommitTreeListTestUtils} from '../testQueries';
b69ab3117import {
b69ab3118 closeCommitInfoSidebar,
b69ab3119 COMMIT,
b69ab3120 dragAndDropCommits,
b69ab3121 expectMessageSentToServer,
b69ab3122 expectYouAreHerePointAt,
b69ab3123 getLastMessageOfTypeSentToServer,
b69ab3124 resetTestMessages,
b69ab3125 simulateCommits,
b69ab3126 simulateMessageFromServer,
b69ab3127 simulateRepoConnected,
b69ab3128 TEST_COMMIT_HISTORY,
b69ab3129} from '../testUtils';
b69ab3130
b69ab3131const {clickGoto} = CommitTreeListTestUtils;
b69ab3132
b69ab3133const abortButton = () => screen.queryByTestId('abort-button');
b69ab3134
b69ab3135describe('operations', () => {
b69ab3136 beforeEach(() => {
b69ab3137 jest.useFakeTimers();
b69ab3138 resetTestMessages();
b69ab3139 render(<App />);
b69ab3140 act(() => {
b69ab3141 closeCommitInfoSidebar();
b69ab3142 expectMessageSentToServer({
b69ab3143 type: 'subscribe',
b69ab3144 kind: 'smartlogCommits',
b69ab3145 subscriptionID: expect.anything(),
b69ab3146 });
b69ab3147 simulateRepoConnected();
b69ab3148 simulateCommits({
b69ab3149 value: TEST_COMMIT_HISTORY,
b69ab3150 });
b69ab3151 });
b69ab3152 });
b69ab3153
b69ab3154 afterEach(() => {
b69ab3155 jest.useRealTimers();
b69ab3156 });
b69ab3157
b69ab3158 it('shows running operation', async () => {
b69ab3159 await clickGoto('c');
b69ab3160
b69ab3161 expect(
b69ab3162 within(screen.getByTestId('progress-container')).getByText('sl goto --rev c'),
b69ab3163 ).toBeInTheDocument();
b69ab3164 });
b69ab3165
b69ab3166 it('shows stdout from running command', async () => {
b69ab3167 await clickGoto('c');
b69ab3168 const message = await waitFor(() =>
b69ab3169 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab3170 );
b69ab3171 const id = message.operation.id;
b69ab3172
b69ab3173 act(() => {
b69ab3174 simulateMessageFromServer({
b69ab3175 type: 'operationProgress',
b69ab3176 id,
b69ab3177 kind: 'spawn',
b69ab3178 queue: [],
b69ab3179 });
b69ab3180
b69ab3181 simulateMessageFromServer({
b69ab3182 type: 'operationProgress',
b69ab3183 id,
b69ab3184 kind: 'stdout',
b69ab3185 message: 'some progress...',
b69ab3186 });
b69ab3187 });
b69ab3188
b69ab3189 expect(screen.queryByText('some progress...')).toBeInTheDocument();
b69ab3190
b69ab3191 act(() => {
b69ab3192 simulateMessageFromServer({
b69ab3193 type: 'operationProgress',
b69ab3194 id,
b69ab3195 kind: 'stdout',
b69ab3196 message: 'another message',
b69ab3197 });
b69ab3198 });
b69ab3199
b69ab31100 expect(screen.queryByText('another message', {exact: false})).toBeInTheDocument();
b69ab31101 });
b69ab31102
b69ab31103 it('shows stderr from running command', async () => {
b69ab31104 await clickGoto('c');
b69ab31105 const message = await waitFor(() =>
b69ab31106 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31107 );
b69ab31108 const id = message.operation.id;
b69ab31109
b69ab31110 act(() => {
b69ab31111 simulateMessageFromServer({
b69ab31112 type: 'operationProgress',
b69ab31113 id,
b69ab31114 kind: 'spawn',
b69ab31115 queue: [],
b69ab31116 });
b69ab31117
b69ab31118 simulateMessageFromServer({
b69ab31119 type: 'operationProgress',
b69ab31120 id,
b69ab31121 kind: 'stderr',
b69ab31122 message: 'some progress...',
b69ab31123 });
b69ab31124 });
b69ab31125
b69ab31126 expect(screen.queryByText('some progress...', {exact: false})).toBeInTheDocument();
b69ab31127
b69ab31128 act(() => {
b69ab31129 simulateMessageFromServer({
b69ab31130 type: 'operationProgress',
b69ab31131 id,
b69ab31132 kind: 'stderr',
b69ab31133 message: 'another message',
b69ab31134 });
b69ab31135 });
b69ab31136
b69ab31137 expect(screen.queryByText('another message', {exact: false})).toBeInTheDocument();
b69ab31138 });
b69ab31139
b69ab31140 it('shows abort on long-running commands', async () => {
b69ab31141 await clickGoto('c');
b69ab31142 expect(abortButton()).toBeNull();
b69ab31143
b69ab31144 act(() => {
b69ab31145 jest.advanceTimersByTime(600000);
b69ab31146 });
b69ab31147 expect(abortButton()).toBeInTheDocument();
b69ab31148 });
b69ab31149
b69ab31150 it('shows successful exit status', async () => {
b69ab31151 await clickGoto('c');
b69ab31152 const message = await waitFor(() =>
b69ab31153 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31154 );
b69ab31155 const id = message.operation.id;
b69ab31156
b69ab31157 act(() => {
b69ab31158 simulateMessageFromServer({
b69ab31159 type: 'operationProgress',
b69ab31160 id,
b69ab31161 kind: 'spawn',
b69ab31162 queue: [],
b69ab31163 });
b69ab31164
b69ab31165 simulateMessageFromServer({
b69ab31166 type: 'operationProgress',
b69ab31167 id,
b69ab31168 kind: 'exit',
b69ab31169 exitCode: 0,
b69ab31170 timestamp: 1234,
b69ab31171 });
b69ab31172 });
b69ab31173
b69ab31174 expect(screen.queryByLabelText('Command exited successfully')).toBeInTheDocument();
b69ab31175 expect(
b69ab31176 within(screen.getByTestId('progress-container')).getByText('sl goto --rev c'),
b69ab31177 ).toBeInTheDocument();
b69ab31178 });
b69ab31179
b69ab31180 it('shows unsuccessful exit status', async () => {
b69ab31181 await clickGoto('c');
b69ab31182 const message = await waitFor(() =>
b69ab31183 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31184 );
b69ab31185 const id = message.operation.id;
b69ab31186
b69ab31187 act(() => {
b69ab31188 simulateMessageFromServer({
b69ab31189 type: 'operationProgress',
b69ab31190 id,
b69ab31191 kind: 'spawn',
b69ab31192 queue: [],
b69ab31193 });
b69ab31194
b69ab31195 simulateMessageFromServer({
b69ab31196 type: 'operationProgress',
b69ab31197 id,
b69ab31198 kind: 'exit',
b69ab31199 exitCode: -1,
b69ab31200 timestamp: 1234,
b69ab31201 });
b69ab31202 });
b69ab31203
b69ab31204 expect(screen.queryByLabelText('Command exited unsuccessfully')).toBeInTheDocument();
b69ab31205 expect(
b69ab31206 within(screen.getByTestId('progress-container')).getByText('sl goto --rev c'),
b69ab31207 ).toBeInTheDocument();
b69ab31208 });
b69ab31209
b69ab31210 it('handles out of order exit messages', async () => {
b69ab31211 await clickGoto('c');
b69ab31212 const message1 = await waitFor(() =>
b69ab31213 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31214 );
b69ab31215 const id1 = message1.operation.id;
b69ab31216
b69ab31217 act(() => {
b69ab31218 simulateMessageFromServer({
b69ab31219 type: 'operationProgress',
b69ab31220 id: id1,
b69ab31221 kind: 'spawn',
b69ab31222 queue: [],
b69ab31223 });
b69ab31224 });
b69ab31225
b69ab31226 await clickGoto('d');
b69ab31227 const message2 = await waitFor(() =>
b69ab31228 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31229 );
b69ab31230 const id2 = message2.operation.id;
b69ab31231
b69ab31232 act(() => {
b69ab31233 simulateMessageFromServer({
b69ab31234 type: 'operationProgress',
b69ab31235 id: id2,
b69ab31236 kind: 'spawn',
b69ab31237 queue: [],
b69ab31238 });
b69ab31239 });
b69ab31240
b69ab31241 // get an exit for the SECOND operation before the first
b69ab31242 act(() => {
b69ab31243 simulateMessageFromServer({
b69ab31244 type: 'operationProgress',
b69ab31245 id: id2,
b69ab31246 kind: 'exit',
b69ab31247 exitCode: 0,
b69ab31248 timestamp: 1234,
b69ab31249 });
b69ab31250 });
b69ab31251
b69ab31252 // but then get the first
b69ab31253 act(() => {
b69ab31254 simulateMessageFromServer({
b69ab31255 type: 'operationProgress',
b69ab31256 id: id1,
b69ab31257 kind: 'exit',
b69ab31258 exitCode: 0,
b69ab31259 timestamp: 1234,
b69ab31260 });
b69ab31261 });
b69ab31262
b69ab31263 // This test is a bit bad: we directly read the jotai state instead of asserting on the UI state.
b69ab31264 // This is to make sure our state is correct, and isn't represented in the UI in an obvious way.
b69ab31265 const opList = readAtom(operationList);
b69ab31266
b69ab31267 expect(opList.currentOperation).toEqual(
b69ab31268 expect.objectContaining({
b69ab31269 operation: expect.objectContaining({id: id2}),
b69ab31270 exitCode: 0,
b69ab31271 }),
b69ab31272 );
b69ab31273 expect(opList.operationHistory).toEqual([
b69ab31274 expect.objectContaining({
b69ab31275 operation: expect.objectContaining({id: id1}),
b69ab31276 exitCode: 0, // we marked it as exited even though they came out of order
b69ab31277 }),
b69ab31278 ]);
b69ab31279
b69ab31280 if (Internal.sendAnalyticsDataToServer != null) {
b69ab31281 expectMessageSentToServer({
b69ab31282 type: 'track',
b69ab31283 data: expect.objectContaining({
b69ab31284 eventName: 'ExitMessageOutOfOrder',
b69ab31285 }),
b69ab31286 });
b69ab31287 }
b69ab31288 });
b69ab31289
b69ab31290 it('reacts to abort', async () => {
b69ab31291 await clickGoto('c');
b69ab31292 const message = await waitFor(() =>
b69ab31293 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31294 );
b69ab31295 const id = message.operation.id;
b69ab31296
b69ab31297 act(() => {
b69ab31298 jest.advanceTimersByTime(600000);
b69ab31299 });
b69ab31300
b69ab31301 // Start abort
b69ab31302 fireEvent.click(abortButton() as Element);
b69ab31303
b69ab31304 // During abort
b69ab31305 expect(abortButton()).toBeDisabled();
b69ab31306
b69ab31307 // After abort (process exit)
b69ab31308 act(() => {
b69ab31309 simulateMessageFromServer({
b69ab31310 type: 'operationProgress',
b69ab31311 id,
b69ab31312 kind: 'exit',
b69ab31313 exitCode: 130,
b69ab31314 timestamp: 1234,
b69ab31315 });
b69ab31316 });
b69ab31317 expect(abortButton()).toBeNull();
b69ab31318 expect(screen.queryByLabelText('Command aborted')).toBeInTheDocument();
b69ab31319 });
b69ab31320
b69ab31321 describe('queued commands', () => {
b69ab31322 it('optimistically shows queued commands', async () => {
b69ab31323 await clickGoto('c');
b69ab31324 const message1 = await waitFor(() =>
b69ab31325 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31326 );
b69ab31327 const id1 = message1.operation.id;
b69ab31328
b69ab31329 act(() => {
b69ab31330 simulateMessageFromServer({
b69ab31331 type: 'operationProgress',
b69ab31332 id: id1,
b69ab31333 kind: 'spawn',
b69ab31334 queue: [],
b69ab31335 });
b69ab31336 });
b69ab31337
b69ab31338 await clickGoto('a');
b69ab31339 await clickGoto('b');
b69ab31340
b69ab31341 expect(
b69ab31342 within(screen.getByTestId('queued-commands')).getByText('sl goto --rev a'),
b69ab31343 ).toBeInTheDocument();
b69ab31344 expect(
b69ab31345 within(screen.getByTestId('queued-commands')).getByText('sl goto --rev b'),
b69ab31346 ).toBeInTheDocument();
b69ab31347 });
b69ab31348
b69ab31349 it('dequeues when the server starts the next command', async () => {
b69ab31350 await clickGoto('c');
b69ab31351 const message1 = await waitFor(() =>
b69ab31352 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31353 );
b69ab31354 const id1 = message1.operation.id;
b69ab31355
b69ab31356 act(() => {
b69ab31357 simulateMessageFromServer({
b69ab31358 type: 'operationProgress',
b69ab31359 id: id1,
b69ab31360 kind: 'spawn',
b69ab31361 queue: [],
b69ab31362 });
b69ab31363 });
b69ab31364
b69ab31365 await clickGoto('a');
b69ab31366 const message2 = await waitFor(() =>
b69ab31367 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31368 );
b69ab31369 const id2 = message2.operation.id;
b69ab31370
b69ab31371 expect(
b69ab31372 within(screen.getByTestId('queued-commands')).getByText('sl goto --rev a'),
b69ab31373 ).toBeInTheDocument();
b69ab31374
b69ab31375 act(() => {
b69ab31376 simulateMessageFromServer({
b69ab31377 type: 'operationProgress',
b69ab31378 id: id2,
b69ab31379 kind: 'spawn',
b69ab31380 queue: [],
b69ab31381 });
b69ab31382 });
b69ab31383
b69ab31384 expect(screen.queryByTestId('queued-commands')).not.toBeInTheDocument();
b69ab31385 });
b69ab31386
b69ab31387 it('takes queued command info from server', async () => {
b69ab31388 await clickGoto('c');
b69ab31389 const message1 = await waitFor(() =>
b69ab31390 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31391 );
b69ab31392 const id1 = message1.operation.id;
b69ab31393
b69ab31394 act(() => {
b69ab31395 simulateMessageFromServer({
b69ab31396 type: 'operationProgress',
b69ab31397 id: id1,
b69ab31398 kind: 'spawn',
b69ab31399 queue: [],
b69ab31400 });
b69ab31401 });
b69ab31402
b69ab31403 await clickGoto('a');
b69ab31404 const message2 = await waitFor(() =>
b69ab31405 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31406 );
b69ab31407 const id2 = message2.operation.id;
b69ab31408
b69ab31409 await clickGoto('b');
b69ab31410 const message3 = await waitFor(() =>
b69ab31411 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31412 );
b69ab31413 const id3 = message3.operation.id;
b69ab31414
b69ab31415 act(() => {
b69ab31416 simulateMessageFromServer({
b69ab31417 type: 'operationProgress',
b69ab31418 id: id1,
b69ab31419 kind: 'exit',
b69ab31420 exitCode: 0,
b69ab31421 timestamp: 1234,
b69ab31422 });
b69ab31423 simulateMessageFromServer({
b69ab31424 type: 'operationProgress',
b69ab31425 id: id2,
b69ab31426 kind: 'spawn',
b69ab31427 queue: [id3],
b69ab31428 });
b69ab31429 });
b69ab31430
b69ab31431 expect(
b69ab31432 within(screen.getByTestId('queued-commands')).getByText('sl goto --rev b'),
b69ab31433 ).toBeInTheDocument();
b69ab31434 expect(
b69ab31435 within(screen.getByTestId('queued-commands')).queryByText('sl goto --rev a'),
b69ab31436 ).not.toBeInTheDocument();
b69ab31437 });
b69ab31438
b69ab31439 it('error running command cancels queued commands', async () => {
b69ab31440 await clickGoto('c');
b69ab31441 const message1 = await waitFor(() =>
b69ab31442 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31443 );
b69ab31444 const id1 = message1.operation.id;
b69ab31445
b69ab31446 act(() => {
b69ab31447 simulateMessageFromServer({
b69ab31448 type: 'operationProgress',
b69ab31449 id: id1,
b69ab31450 kind: 'spawn',
b69ab31451 queue: [],
b69ab31452 });
b69ab31453 });
b69ab31454
b69ab31455 await clickGoto('a');
b69ab31456 await clickGoto('b');
b69ab31457
b69ab31458 expect(screen.queryByTestId('queued-commands')).toBeInTheDocument();
b69ab31459 expect(screen.queryByText('Next to run')).toBeInTheDocument();
b69ab31460 act(() => {
b69ab31461 // original goto fails
b69ab31462 simulateMessageFromServer({
b69ab31463 type: 'operationProgress',
b69ab31464 id: id1,
b69ab31465 kind: 'exit',
b69ab31466 exitCode: -1,
b69ab31467 timestamp: 1234,
b69ab31468 });
b69ab31469 });
b69ab31470 expect(screen.getByTestId('cancelled-queued-commands')).toBeInTheDocument();
b69ab31471 expect(screen.queryByText('Next to run')).not.toBeInTheDocument();
b69ab31472 });
b69ab31473
b69ab31474 it('force clears optimistic state after fetching after an operation has finished', async () => {
b69ab31475 jest.spyOn(tracker, 'track').mockImplementation(() => null);
b69ab31476 const commitsBeforeOperations = {
b69ab31477 value: [
b69ab31478 COMMIT('e', 'Commit E', 'd', {isDot: true}),
b69ab31479 COMMIT('d', 'Commit D', 'c'),
b69ab31480 COMMIT('c', 'Commit C', 'b'),
b69ab31481 COMMIT('b', 'Commit B', 'a'),
b69ab31482 COMMIT('a', 'Commit A', '1'),
b69ab31483 COMMIT('1', 'public', '0', {phase: 'public'}),
b69ab31484 ],
b69ab31485 };
b69ab31486 const commitsAfterOperations = {
b69ab31487 value: [
b69ab31488 COMMIT('e2', 'Commit E', 'd2'),
b69ab31489 COMMIT('d2', 'Commit D', 'c2', {isDot: true}), // goto
b69ab31490 COMMIT('c2', 'Commit C', 'a'), // rebased
b69ab31491 COMMIT('b', 'Commit B', 'a'),
b69ab31492 COMMIT('a', 'Commit A', '1'),
b69ab31493 COMMIT('1', 'public', '0', {phase: 'public'}),
b69ab31494 ],
b69ab31495 };
b69ab31496
b69ab31497 act(() =>
b69ab31498 simulateMessageFromServer({
b69ab31499 type: 'subscriptionResult',
b69ab31500 kind: 'smartlogCommits',
b69ab31501 subscriptionID: mostRecentSubscriptionIds.smartlogCommits,
b69ab31502 data: {
b69ab31503 fetchStartTimestamp: 1,
b69ab31504 fetchCompletedTimestamp: 2,
b69ab31505 commits: commitsBeforeOperations,
b69ab31506 },
b69ab31507 }),
b69ab31508 );
b69ab31509
b69ab31510 // 100 200 300 400 500 600 700
b69ab31511 // |--------|--------|--------|--------|--------|--------|
b69ab31512 // <----- rebase ---->
b69ab31513 // ...................<----- goto ----->
b69ab31514 // <----fetch1---> (no effect)
b69ab31515 // <---fetch2--> (clears optimistic state)
b69ab31516
b69ab31517 // t=100 simulate spawn rebase [c-d-e(YouAreHere)] -> a
b69ab31518 // t=200 simulate queue goto 'd' (successor: 'd2')
b69ab31519 // t=300 simulate exit rebase (success)
b69ab31520 // t=400 simulate spawn goto
b69ab31521 // t=500 simulate exit goto (success)
b69ab31522 // no "commitsAfterOperations" state received
b69ab31523 // expect optimistic "You are here" to be on the old 'e'
b69ab31524 // t=600 simulate new commits fetch started @ t=450, with new head
b69ab31525 // no effect
b69ab31526 // t=700 simulate new commits fetch started @ t=550, with new head
b69ab31527 // BEFORE: Optimistic state wouldn't resolve, so "You were here..." would stick
b69ab31528 // AFTER: Optimistic state forced to resolve, so "You were here..." is gone
b69ab31529
b69ab31530 dragAndDropCommits('c', 'a');
b69ab31531 fireEvent.click(screen.getByText('Run Rebase'));
b69ab31532 await waitFor(() => {
b69ab31533 expect(screen.getByText('rebasing...')).toBeInTheDocument();
b69ab31534 });
b69ab31535
b69ab31536 // Get the rebase operation ID
b69ab31537 const rebaseMessage = await waitFor(() =>
b69ab31538 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31539 );
b69ab31540 const rebaseId = rebaseMessage.operation.id;
b69ab31541
b69ab31542 await clickGoto('d'); // checkout d, which is now optimistic from the rebase, since it'll actually become d2.
b69ab31543
b69ab31544 // Get the goto operation ID
b69ab31545 const gotoMessage = await waitFor(() =>
b69ab31546 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31547 );
b69ab31548 const gotoId = gotoMessage.operation.id;
b69ab31549
b69ab31550 act(() =>
b69ab31551 simulateMessageFromServer({
b69ab31552 type: 'operationProgress',
b69ab31553 id: rebaseId,
b69ab31554 kind: 'spawn',
b69ab31555 queue: [],
b69ab31556 }),
b69ab31557 );
b69ab31558 act(() =>
b69ab31559 simulateMessageFromServer({
b69ab31560 type: 'operationProgress',
b69ab31561 id: gotoId,
b69ab31562 kind: 'queue',
b69ab31563 queue: [gotoId],
b69ab31564 }),
b69ab31565 );
b69ab31566 act(() =>
b69ab31567 simulateMessageFromServer({
b69ab31568 type: 'operationProgress',
b69ab31569 id: rebaseId,
b69ab31570 kind: 'exit',
b69ab31571 exitCode: 0,
b69ab31572 timestamp: 300,
b69ab31573 }),
b69ab31574 );
b69ab31575 act(() =>
b69ab31576 simulateMessageFromServer({
b69ab31577 type: 'operationProgress',
b69ab31578 id: gotoId,
b69ab31579 kind: 'spawn',
b69ab31580 queue: [],
b69ab31581 }),
b69ab31582 );
b69ab31583 act(() =>
b69ab31584 simulateMessageFromServer({
b69ab31585 type: 'operationProgress',
b69ab31586 id: gotoId,
b69ab31587 kind: 'exit',
b69ab31588 exitCode: 0,
b69ab31589 timestamp: 500,
b69ab31590 }),
b69ab31591 );
b69ab31592 act(() =>
b69ab31593 simulateMessageFromServer({
b69ab31594 type: 'operationProgress',
b69ab31595 id: gotoId,
b69ab31596 kind: 'exit',
b69ab31597 exitCode: 0,
b69ab31598 timestamp: 500,
b69ab31599 }),
b69ab31600 );
b69ab31601
b69ab31602 act(() =>
b69ab31603 simulateMessageFromServer({
b69ab31604 type: 'subscriptionResult',
b69ab31605 kind: 'smartlogCommits',
b69ab31606 subscriptionID: mostRecentSubscriptionIds.smartlogCommits,
b69ab31607 data: {
b69ab31608 commits: commitsBeforeOperations, // not observed the new head
b69ab31609 fetchStartTimestamp: 400, // before goto finished
b69ab31610 fetchCompletedTimestamp: 450,
b69ab31611 },
b69ab31612 }),
b69ab31613 );
b69ab31614
b69ab31615 // this latest fetch started before the goto finished, so we don't know that it has all the information
b69ab31616 // included. So the optimistic state remains (goto 'd').
b69ab31617 expectYouAreHerePointAt('d');
b69ab31618
b69ab31619 act(() =>
b69ab31620 simulateMessageFromServer({
b69ab31621 type: 'subscriptionResult',
b69ab31622 kind: 'smartlogCommits',
b69ab31623 subscriptionID: mostRecentSubscriptionIds.smartlogCommits,
b69ab31624 data: {
b69ab31625 commits: commitsAfterOperations, // observed the new head
b69ab31626 fetchStartTimestamp: 400, // before goto finished
b69ab31627 fetchCompletedTimestamp: 450,
b69ab31628 },
b69ab31629 }),
b69ab31630 );
b69ab31631
b69ab31632 // However, even if the latest fetch started before the goto finished,
b69ab31633 // if "goto" saw that head = the new commit, the optimistic state is a
b69ab31634 // no-op (does not update 'd2' from the smartlog head back to 'd').
b69ab31635 expectYouAreHerePointAt('d2');
b69ab31636
b69ab31637 act(() =>
b69ab31638 simulateMessageFromServer({
b69ab31639 type: 'subscriptionResult',
b69ab31640 kind: 'smartlogCommits',
b69ab31641 subscriptionID: mostRecentSubscriptionIds.smartlogCommits,
b69ab31642 data: {
b69ab31643 commits: commitsBeforeOperations, // intentionally "incorrect" to test the force clear out
b69ab31644 fetchStartTimestamp: 550, // after goto finished
b69ab31645 fetchCompletedTimestamp: 600,
b69ab31646 },
b69ab31647 }),
b69ab31648 );
b69ab31649
b69ab31650 // This latest fetch started AFTER the goto finished, so we can be sure
b69ab31651 // it accounts for that operation.
b69ab31652 // So the optimistic state should be cleared out, even though we didn't
b69ab31653 // detect that the optimistic state should have resolved according to the applier.
b69ab31654 // (does not update 'e' from the smartlog head to 'd')
b69ab31655 expectYouAreHerePointAt('e');
b69ab31656 });
b69ab31657 });
b69ab31658
b69ab31659 describe('progress messages', () => {
b69ab31660 it('shows progress messages', async () => {
b69ab31661 await clickGoto('c');
b69ab31662 const message = await waitFor(() =>
b69ab31663 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31664 );
b69ab31665 const id = message.operation.id;
b69ab31666
b69ab31667 act(() => {
b69ab31668 simulateMessageFromServer({
b69ab31669 type: 'operationProgress',
b69ab31670 id,
b69ab31671 kind: 'spawn',
b69ab31672 queue: [],
b69ab31673 });
b69ab31674
b69ab31675 simulateMessageFromServer({
b69ab31676 type: 'operationProgress',
b69ab31677 id,
b69ab31678 kind: 'progress',
b69ab31679 progress: {message: 'doing the thing', progress: 3, progressTotal: 7},
b69ab31680 });
b69ab31681 });
b69ab31682
b69ab31683 expect(
b69ab31684 within(screen.getByTestId('progress-container')).getByText('doing the thing'),
b69ab31685 ).toBeInTheDocument();
b69ab31686 });
b69ab31687
b69ab31688 it('hide progress on new stdout', async () => {
b69ab31689 await clickGoto('c');
b69ab31690 const message = await waitFor(() =>
b69ab31691 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31692 );
b69ab31693 const id = message.operation.id;
b69ab31694
b69ab31695 act(() => {
b69ab31696 simulateMessageFromServer({
b69ab31697 type: 'operationProgress',
b69ab31698 id,
b69ab31699 kind: 'spawn',
b69ab31700 queue: [],
b69ab31701 });
b69ab31702
b69ab31703 simulateMessageFromServer({
b69ab31704 type: 'operationProgress',
b69ab31705 id,
b69ab31706 kind: 'progress',
b69ab31707 progress: {message: 'doing the thing'},
b69ab31708 });
b69ab31709 });
b69ab31710
b69ab31711 expect(
b69ab31712 within(screen.getByTestId('progress-container')).getByText('doing the thing'),
b69ab31713 ).toBeInTheDocument();
b69ab31714
b69ab31715 act(() => {
b69ab31716 simulateMessageFromServer({
b69ab31717 type: 'operationProgress',
b69ab31718 id,
b69ab31719 kind: 'stdout',
b69ab31720 message: 'hello',
b69ab31721 });
b69ab31722 });
b69ab31723
b69ab31724 expect(
b69ab31725 within(screen.getByTestId('progress-container')).queryByText('doing the thing'),
b69ab31726 ).not.toBeInTheDocument();
b69ab31727 expect(
b69ab31728 within(screen.getByTestId('progress-container')).getByText('hello'),
b69ab31729 ).toBeInTheDocument();
b69ab31730 });
b69ab31731 });
b69ab31732
b69ab31733 describe('inline progress', () => {
b69ab31734 it('shows progress messages next to commits', async () => {
b69ab31735 await clickGoto('c');
b69ab31736 const message = await waitFor(() =>
b69ab31737 utils.nullthrows(getLastMessageOfTypeSentToServer('runOperation')),
b69ab31738 );
b69ab31739 const id = message.operation.id;
b69ab31740
b69ab31741 act(() => {
b69ab31742 simulateMessageFromServer({
b69ab31743 type: 'operationProgress',
b69ab31744 id,
b69ab31745 kind: 'spawn',
b69ab31746 queue: [],
b69ab31747 });
b69ab31748
b69ab31749 simulateMessageFromServer({
b69ab31750 type: 'operationProgress',
b69ab31751 id,
b69ab31752 kind: 'inlineProgress',
b69ab31753 hash: 'c',
b69ab31754 message: 'going...', // not a real thing for goto operation, but we support arbitrary progress
b69ab31755 });
b69ab31756 });
b69ab31757
b69ab31758 expect(
b69ab31759 within(screen.getByTestId('commit-tree-root')).getByText('going...'),
b69ab31760 ).toBeInTheDocument();
b69ab31761 });
b69ab31762 });
b69ab31763});