addons/isl/src/__tests__/selection.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} from '@testing-library/react';
b69ab319import userEvent from '@testing-library/user-event';
b69ab3110import App from '../App';
b69ab3111import {readAtom} from '../jotaiUtils';
b69ab3112import {individualToggleKey, selectedCommits} from '../selection';
b69ab3113import {mostRecentSubscriptionIds} from '../serverAPIState';
b69ab3114import {CommitInfoTestUtils, CommitTreeListTestUtils} from '../testQueries';
b69ab3115import {
b69ab3116 closeCommitInfoSidebar,
b69ab3117 COMMIT,
b69ab3118 commitInfoIsOpen,
b69ab3119 expectMessageSentToServer,
b69ab3120 resetTestMessages,
b69ab3121 simulateCommits,
b69ab3122 simulateRepoConnected,
b69ab3123 TEST_COMMIT_HISTORY,
b69ab3124} from '../testUtils';
b69ab3125
b69ab3126describe('selection', () => {
b69ab3127 beforeEach(() => {
b69ab3128 resetTestMessages();
b69ab3129 render(<App />);
b69ab3130 act(() => {
b69ab3131 simulateRepoConnected();
b69ab3132 expectMessageSentToServer({
b69ab3133 type: 'subscribe',
b69ab3134 kind: 'smartlogCommits',
b69ab3135 subscriptionID: mostRecentSubscriptionIds.smartlogCommits,
b69ab3136 });
b69ab3137 simulateCommits({value: TEST_COMMIT_HISTORY});
b69ab3138 });
b69ab3139 });
b69ab3140
b69ab3141 const click = (
b69ab3142 name: string,
b69ab3143 opts?: {shiftKey?: boolean; metaKey?: boolean; ctrlKey?: boolean},
b69ab3144 ) => {
b69ab3145 act(
b69ab3146 () => void fireEvent.click(CommitTreeListTestUtils.withinCommitTree().getByText(name), opts),
b69ab3147 );
b69ab3148 };
b69ab3149
b69ab3150 const expectNoRealSelection = () =>
b69ab3151 expect(CommitInfoTestUtils.withinCommitInfo().queryAllByTestId('selected-commit')).toHaveLength(
b69ab3152 0,
b69ab3153 );
b69ab3154
b69ab3155 const expectOnlyOneCommitSelected = () =>
b69ab3156 expect(
b69ab3157 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab3158 ).not.toBeInTheDocument();
b69ab3159
b69ab3160 const expectNCommitsSelected = (n: number) =>
b69ab3161 expect(
b69ab3162 CommitInfoTestUtils.withinCommitInfo().queryByText(`${n} Commits Selected`),
b69ab3163 ).toBeInTheDocument();
b69ab3164
b69ab3165 const upArrow = (shift?: boolean) => {
b69ab3166 act(() =>
b69ab3167 userEvent.type(
b69ab3168 screen.getByTestId('commit-tree-root'),
b69ab3169 (shift ? '{shift}' : '') + '{arrowup}',
b69ab3170 ),
b69ab3171 );
b69ab3172 };
b69ab3173 const downArrow = (shift?: boolean) => {
b69ab3174 act(() =>
b69ab3175 userEvent.type(
b69ab3176 screen.getByTestId('commit-tree-root'),
b69ab3177 (shift ? '{shift}' : '') + '{arrowdown}',
b69ab3178 ),
b69ab3179 );
b69ab3180 };
b69ab3181
b69ab3182 const rightArrow = () => {
b69ab3183 act(() => userEvent.type(screen.getByTestId('commit-tree-root'), '{arrowright}'));
b69ab3184 };
b69ab3185
b69ab3186 it('allows selecting via click', () => {
b69ab3187 act(() => void fireEvent.click(screen.getByText('Commit A')));
b69ab3188
b69ab3189 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab3190 });
b69ab3191
b69ab3192 it("can't select public commits", () => {
b69ab3193 act(() => void fireEvent.click(screen.getByText('remote/master')));
b69ab3194 // it remains selecting the head commit
b69ab3195 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit E')).toBeInTheDocument();
b69ab3196 });
b69ab3197
b69ab3198 it('click on different commits changes selection', () => {
b69ab3199 act(() => void fireEvent.click(screen.getByText('Commit A')));
b69ab31100 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab31101 act(() => void fireEvent.click(screen.getByText('Commit B')));
b69ab31102 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit B')).toBeInTheDocument();
b69ab31103
b69ab31104 // not a multi-selection
b69ab31105 expect(
b69ab31106 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab31107 ).not.toBeInTheDocument();
b69ab31108 });
b69ab31109
b69ab31110 it('allows multi-selecting via cmd-click', () => {
b69ab31111 act(() => void fireEvent.click(screen.getByText('Commit A'), {[individualToggleKey]: true}));
b69ab31112 act(() => void fireEvent.click(screen.getByText('Commit B'), {[individualToggleKey]: true}));
b69ab31113 act(() => void fireEvent.click(screen.getByText('Commit C'), {[individualToggleKey]: true}));
b69ab31114
b69ab31115 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab31116 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit B')).toBeInTheDocument();
b69ab31117 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31118 expect(
b69ab31119 CommitInfoTestUtils.withinCommitInfo().getByText('3 Commits Selected'),
b69ab31120 ).toBeInTheDocument();
b69ab31121 });
b69ab31122
b69ab31123 it('single click after multi-select resets to single selection', () => {
b69ab31124 act(() => void fireEvent.click(screen.getByText('Commit A'), {[individualToggleKey]: true}));
b69ab31125 act(() => void fireEvent.click(screen.getByText('Commit B'), {[individualToggleKey]: true}));
b69ab31126
b69ab31127 act(() => void fireEvent.click(screen.getByText('Commit C')));
b69ab31128
b69ab31129 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Commit A')).not.toBeInTheDocument();
b69ab31130 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Commit B')).not.toBeInTheDocument();
b69ab31131 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31132
b69ab31133 // not a multi-selection
b69ab31134 expect(
b69ab31135 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab31136 ).not.toBeInTheDocument();
b69ab31137 });
b69ab31138
b69ab31139 it('clicking on a commit a second time deselects it', () => {
b69ab31140 const commitA = screen.getByText('Commit A');
b69ab31141 act(() => void fireEvent.click(commitA));
b69ab31142 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab31143 act(() => void fireEvent.click(commitA));
b69ab31144 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Commit A')).not.toBeInTheDocument();
b69ab31145 });
b69ab31146
b69ab31147 it('cmd-clicking on a commit a second time deselects it', () => {
b69ab31148 const commitA = screen.getByText('Commit A');
b69ab31149 act(() => void fireEvent.click(commitA, {[individualToggleKey]: true}));
b69ab31150 act(() => void fireEvent.click(screen.getByText('Commit B'), {[individualToggleKey]: true}));
b69ab31151 act(() => void fireEvent.click(screen.getByText('Commit C'), {[individualToggleKey]: true}));
b69ab31152
b69ab31153 act(() => void fireEvent.click(commitA, {[individualToggleKey]: true}));
b69ab31154
b69ab31155 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Commit A')).not.toBeInTheDocument();
b69ab31156 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit B')).toBeInTheDocument();
b69ab31157 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31158 expect(
b69ab31159 CommitInfoTestUtils.withinCommitInfo().getByText('2 Commits Selected'),
b69ab31160 ).toBeInTheDocument();
b69ab31161 });
b69ab31162
b69ab31163 it('single click after multi-select resets to single selection, even on a previously selected commits', () => {
b69ab31164 const commitA = screen.getByText('Commit A');
b69ab31165 act(() => void fireEvent.click(commitA, {[individualToggleKey]: true}));
b69ab31166 act(() => void fireEvent.click(screen.getByText('Commit B'), {[individualToggleKey]: true}));
b69ab31167 act(() => void fireEvent.click(screen.getByText('Commit C'), {[individualToggleKey]: true}));
b69ab31168
b69ab31169 act(() => void fireEvent.click(commitA));
b69ab31170
b69ab31171 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab31172 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Commit B')).not.toBeInTheDocument();
b69ab31173 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Commit C')).not.toBeInTheDocument();
b69ab31174
b69ab31175 // not a multi-selection
b69ab31176 expect(
b69ab31177 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab31178 ).not.toBeInTheDocument();
b69ab31179 });
b69ab31180
b69ab31181 it("selecting a commit that's no longer available does not render", () => {
b69ab31182 // add a new commit F, then select it
b69ab31183 act(() => simulateCommits({value: [COMMIT('f', 'Commit F', 'e'), ...TEST_COMMIT_HISTORY]}));
b69ab31184 act(() => void fireEvent.click(screen.getByText('Commit F')));
b69ab31185 // remove that commit from the history
b69ab31186 act(() => simulateCommits({value: TEST_COMMIT_HISTORY}));
b69ab31187
b69ab31188 // F no longer exists to show, so now instead the head commit is selected
b69ab31189 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit E')).toBeInTheDocument();
b69ab31190
b69ab31191 // not a multi-selection
b69ab31192 expect(
b69ab31193 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab31194 ).not.toBeInTheDocument();
b69ab31195 });
b69ab31196
b69ab31197 it('does not show the submit button for multi selections in GitHub repos', () => {
b69ab31198 act(() => void fireEvent.click(screen.getByText('Commit A'), {[individualToggleKey]: true}));
b69ab31199 act(() => void fireEvent.click(screen.getByText('Commit B'), {[individualToggleKey]: true}));
b69ab31200 expect(
b69ab31201 CommitInfoTestUtils.withinCommitInfo().queryByText('Submit Selected Commits'),
b69ab31202 ).not.toBeInTheDocument();
b69ab31203 });
b69ab31204
b69ab31205 it("multi selection commit previews doesn't include uncommitted changes", () => {
b69ab31206 act(
b69ab31207 () =>
b69ab31208 void fireEvent.click(CommitTreeListTestUtils.withinCommitTree().getByText('Commit E'), {
b69ab31209 [individualToggleKey]: true,
b69ab31210 }),
b69ab31211 );
b69ab31212 act(
b69ab31213 () =>
b69ab31214 void fireEvent.click(CommitTreeListTestUtils.withinCommitTree().getByText('Commit D'), {
b69ab31215 [individualToggleKey]: true,
b69ab31216 }),
b69ab31217 );
b69ab31218 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit E')).toBeInTheDocument();
b69ab31219 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31220
b69ab31221 expect(
b69ab31222 CommitInfoTestUtils.withinCommitInfo().queryByText('You are here'),
b69ab31223 ).not.toBeInTheDocument();
b69ab31224 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Uncommit')).not.toBeInTheDocument();
b69ab31225 expect(CommitInfoTestUtils.withinCommitInfo().queryByText('Go to')).not.toBeInTheDocument();
b69ab31226 });
b69ab31227
b69ab31228 describe('shift click selection', () => {
b69ab31229 it('selects ranges of commits when shift-clicking', () => {
b69ab31230 click('Commit B');
b69ab31231 click('Commit D', {shiftKey: true});
b69ab31232 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit B')).toBeInTheDocument();
b69ab31233 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31234 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31235 expect(
b69ab31236 CommitInfoTestUtils.withinCommitInfo().getByText('3 Commits Selected'),
b69ab31237 ).toBeInTheDocument();
b69ab31238 });
b69ab31239
b69ab31240 it('skips public commits, works across stacks and branches', () => {
b69ab31241 click('Commit D');
b69ab31242 click('Commit Y', {shiftKey: true});
b69ab31243 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31244 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit E')).toBeInTheDocument();
b69ab31245 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit X')).toBeInTheDocument();
b69ab31246 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit Y')).toBeInTheDocument();
b69ab31247 expect(
b69ab31248 CommitInfoTestUtils.withinCommitInfo().getByText('4 Commits Selected'), // skipped '2', the public base of 'Commit X'
b69ab31249 ).toBeInTheDocument();
b69ab31250 });
b69ab31251
b69ab31252 it('adds to selection', () => {
b69ab31253 click('Commit A', {[individualToggleKey]: true});
b69ab31254 click('Commit C', {[individualToggleKey]: true});
b69ab31255 click('Commit E', {shiftKey: true});
b69ab31256 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab31257 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31258 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31259 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit E')).toBeInTheDocument();
b69ab31260 expect(
b69ab31261 CommitInfoTestUtils.withinCommitInfo().getByText('4 Commits Selected'),
b69ab31262 ).toBeInTheDocument();
b69ab31263 });
b69ab31264
b69ab31265 it('prefers dag range to flatten range', () => {
b69ab31266 // a-b--c-d-e
b69ab31267 // \
b69ab31268 // f-g
b69ab31269 act(() =>
b69ab31270 simulateCommits({
b69ab31271 value: [
b69ab31272 COMMIT('f', 'Commit F', 'b'),
b69ab31273 COMMIT('g', 'Commit G', 'f'),
b69ab31274 ...TEST_COMMIT_HISTORY,
b69ab31275 ],
b69ab31276 }),
b69ab31277 );
b69ab31278
b69ab31279 {
b69ab31280 click('Commit A'); // select
b69ab31281 click('Commit G', {shiftKey: true});
b69ab31282 const selected = readAtom(selectedCommits);
b69ab31283 expect([...selected].sort()).toEqual(['a', 'b', 'f', 'g']);
b69ab31284 }
b69ab31285
b69ab31286 {
b69ab31287 click('Commit D'); // select
b69ab31288 click('Commit A', {shiftKey: true});
b69ab31289 const selected = readAtom(selectedCommits);
b69ab31290 expect([...selected].sort()).toEqual(['a', 'b', 'c', 'd']);
b69ab31291 }
b69ab31292 });
b69ab31293
b69ab31294 it('deselecting clears last selected', () => {
b69ab31295 click('Commit A'); // select
b69ab31296 click('Commit A'); // deselect
b69ab31297 click('Commit C', {[individualToggleKey]: true});
b69ab31298 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31299 // just one commit, C, selected
b69ab31300 expect(
b69ab31301 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab31302 ).not.toBeInTheDocument();
b69ab31303 });
b69ab31304
b69ab31305 it('shift clicking when nothing selected acts like normal clicking', () => {
b69ab31306 click('Commit C', {metaKey: true});
b69ab31307 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31308 expect(
b69ab31309 CommitInfoTestUtils.withinCommitInfo().queryByText(/\d Commits Selected/),
b69ab31310 ).not.toBeInTheDocument();
b69ab31311 });
b69ab31312 });
b69ab31313
b69ab31314 describe('up/down arrows to select', () => {
b69ab31315 it('down arrow with no selection starts you at the top', () => {
b69ab31316 expectNoRealSelection();
b69ab31317 downArrow();
b69ab31318 expectOnlyOneCommitSelected();
b69ab31319 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit Z')).toBeInTheDocument();
b69ab31320 });
b69ab31321
b69ab31322 it('up arrow noop if nothing selected', () => {
b69ab31323 upArrow();
b69ab31324 upArrow(true);
b69ab31325 expectNoRealSelection();
b69ab31326 });
b69ab31327
b69ab31328 it('up arrow modifies selection', () => {
b69ab31329 click('Commit C');
b69ab31330 upArrow();
b69ab31331 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31332 expectOnlyOneCommitSelected();
b69ab31333 });
b69ab31334
b69ab31335 it('down arrow modifies selection', () => {
b69ab31336 click('Commit C');
b69ab31337 downArrow();
b69ab31338 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit B')).toBeInTheDocument();
b69ab31339 expectOnlyOneCommitSelected();
b69ab31340 });
b69ab31341
b69ab31342 it('multiple arrow keys keep modifying selection', () => {
b69ab31343 click('Commit A');
b69ab31344 upArrow();
b69ab31345 upArrow();
b69ab31346 upArrow();
b69ab31347 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31348 expectOnlyOneCommitSelected();
b69ab31349 });
b69ab31350
b69ab31351 it('selection skips public commits', () => {
b69ab31352 click('Commit A');
b69ab31353 upArrow(); // B
b69ab31354 upArrow(); // C
b69ab31355 upArrow(); // D
b69ab31356 upArrow(); // E
b69ab31357 upArrow(); // skip public base, go to X
b69ab31358 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit X')).toBeInTheDocument();
b69ab31359 expectOnlyOneCommitSelected();
b69ab31360 });
b69ab31361
b69ab31362 it('goes from last selection if multiple are selected', () => {
b69ab31363 click('Commit A');
b69ab31364 click('Commit C', {metaKey: true});
b69ab31365 upArrow();
b69ab31366 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31367 expectOnlyOneCommitSelected();
b69ab31368 });
b69ab31369
b69ab31370 it('holding shift extends upwards', () => {
b69ab31371 click('Commit C');
b69ab31372 upArrow(/* shift */ true);
b69ab31373 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31374 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit D')).toBeInTheDocument();
b69ab31375 expectNCommitsSelected(2);
b69ab31376 });
b69ab31377
b69ab31378 it('holding shift extends downwards', () => {
b69ab31379 click('Commit C');
b69ab31380 downArrow(/* shift */ true);
b69ab31381 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit C')).toBeInTheDocument();
b69ab31382 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit B')).toBeInTheDocument();
b69ab31383 expectNCommitsSelected(2);
b69ab31384 });
b69ab31385
b69ab31386 it('right arrows opens sidebar', () => {
b69ab31387 click('Commit A');
b69ab31388 act(() => closeCommitInfoSidebar());
b69ab31389
b69ab31390 expect(commitInfoIsOpen()).toEqual(false);
b69ab31391 rightArrow();
b69ab31392 expect(CommitInfoTestUtils.withinCommitInfo().getByText('Commit A')).toBeInTheDocument();
b69ab31393 expect(commitInfoIsOpen()).toEqual(true);
b69ab31394 });
b69ab31395 });
b69ab31396});