addons/isl/src/__tests__/Tooltip.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 type {ReactNode} from 'react';
b69ab319
b69ab3110import {act, fireEvent, render, screen, within} from '@testing-library/react';
b69ab3111import userEvent from '@testing-library/user-event';
b69ab3112import {Tooltip} from 'isl-components/Tooltip';
b69ab3113import {ViewportOverlayRoot} from 'isl-components/ViewportOverlay';
b69ab3114import App from '../App';
b69ab3115import {
b69ab3116 COMMIT,
b69ab3117 closeCommitInfoSidebar,
b69ab3118 expectMessageSentToServer,
b69ab3119 resetTestMessages,
b69ab3120 simulateCommits,
b69ab3121} from '../testUtils';
b69ab3122
b69ab3123/* eslint-disable @typescript-eslint/no-non-null-assertion */
b69ab3124
b69ab3125describe('tooltips in ISL', () => {
b69ab3126 let unmount: () => void;
b69ab3127 beforeEach(() => {
b69ab3128 resetTestMessages();
b69ab3129 unmount = render(<App />).unmount;
b69ab3130
b69ab3131 act(() => {
b69ab3132 closeCommitInfoSidebar();
b69ab3133 expectMessageSentToServer({
b69ab3134 type: 'subscribe',
b69ab3135 kind: 'smartlogCommits',
b69ab3136 subscriptionID: expect.anything(),
b69ab3137 });
b69ab3138 simulateCommits({
b69ab3139 value: [
b69ab3140 COMMIT('1', 'some public base', '0', {phase: 'public'}),
b69ab3141 COMMIT('a', 'My Commit', '1'),
b69ab3142 COMMIT('b', 'Another Commit', 'a', {isDot: true}),
b69ab3143 ],
b69ab3144 });
b69ab3145 });
b69ab3146 });
b69ab3147 afterEach(() => {
b69ab3148 unmount();
b69ab3149 });
b69ab3150
b69ab3151 describe('click to show', () => {
b69ab3152 const clickSettingsGearToMakeTooltip = () => {
b69ab3153 const settingsButtonTooltipCreator =
b69ab3154 screen.getByTestId('settings-gear-button').parentElement!;
b69ab3155 expect(settingsButtonTooltipCreator).toBeInTheDocument();
b69ab3156 act(() => {
b69ab3157 fireEvent.click(settingsButtonTooltipCreator);
b69ab3158 });
b69ab3159 };
b69ab3160
b69ab3161 it('shows settings dropdown when clicked', () => {
b69ab3162 clickSettingsGearToMakeTooltip();
b69ab3163
b69ab3164 const settingsDropdown = within(screen.getByTestId('viewport-overlay-root')).getByTestId(
b69ab3165 'settings-dropdown',
b69ab3166 );
b69ab3167 expect(settingsDropdown).toBeInTheDocument();
b69ab3168 });
b69ab3169
b69ab3170 it('clicking inside tooltip does not dismiss it', () => {
b69ab3171 clickSettingsGearToMakeTooltip();
b69ab3172
b69ab3173 const settingsDropdown = within(screen.getByTestId('viewport-overlay-root')).getByTestId(
b69ab3174 'settings-dropdown',
b69ab3175 );
b69ab3176 const themeDropdown = within(settingsDropdown).getByText('Theme');
b69ab3177 expect(themeDropdown).toBeInTheDocument();
b69ab3178 act(() => {
b69ab3179 fireEvent.click(themeDropdown!);
b69ab3180 });
b69ab3181
b69ab3182 const settingsDropdown2 = within(screen.getByTestId('viewport-overlay-root')).getByTestId(
b69ab3183 'settings-dropdown',
b69ab3184 );
b69ab3185 expect(settingsDropdown2).toBeInTheDocument();
b69ab3186 });
b69ab3187
b69ab3188 it('clicking outside tooltip dismisses it', () => {
b69ab3189 const settingsButton = screen.getByTestId('settings-gear-button');
b69ab3190 act(() => {
b69ab3191 fireEvent.click(settingsButton);
b69ab3192 });
b69ab3193
b69ab3194 const settingsDropdown = within(screen.getByTestId('viewport-overlay-root')).queryByTestId(
b69ab3195 'settings-dropdown',
b69ab3196 );
b69ab3197 expect(settingsDropdown).toBeInTheDocument();
b69ab3198
b69ab3199 act(() => {
b69ab31100 fireEvent.click(screen.getByTestId('commit-a')!);
b69ab31101 });
b69ab31102
b69ab31103 const settingsDropdown2 = within(screen.getByTestId('viewport-overlay-root')).queryByTestId(
b69ab31104 'settings-dropdown',
b69ab31105 );
b69ab31106 expect(settingsDropdown2).not.toBeInTheDocument();
b69ab31107 });
b69ab31108 });
b69ab31109
b69ab31110 describe('hover to show', () => {
b69ab31111 const REFRESH_BUTTON_HOVER_TEXT = 'Re-fetch latest commits and uncommitted changes.';
b69ab31112 it('hovering refresh button shows tooltip', () => {
b69ab31113 const refreshButton = screen.getByTestId('refresh-button').parentElement as HTMLElement;
b69ab31114 userEvent.hover(refreshButton);
b69ab31115
b69ab31116 const refreshButtonTooltip = within(screen.getByTestId('viewport-overlay-root')).getByText(
b69ab31117 REFRESH_BUTTON_HOVER_TEXT,
b69ab31118 );
b69ab31119 expect(refreshButtonTooltip).toBeInTheDocument();
b69ab31120
b69ab31121 userEvent.unhover(refreshButton);
b69ab31122
b69ab31123 expect(
b69ab31124 within(screen.getByTestId('viewport-overlay-root')).queryByText(REFRESH_BUTTON_HOVER_TEXT),
b69ab31125 ).not.toBeInTheDocument();
b69ab31126 });
b69ab31127
b69ab31128 it('escape key dismisses tooltip', () => {
b69ab31129 const refreshButton = screen.getByTestId('refresh-button').parentElement as HTMLElement;
b69ab31130 userEvent.hover(refreshButton);
b69ab31131
b69ab31132 const refreshButtonTooltip = within(screen.getByTestId('viewport-overlay-root')).getByText(
b69ab31133 REFRESH_BUTTON_HOVER_TEXT,
b69ab31134 );
b69ab31135 expect(refreshButtonTooltip).toBeInTheDocument();
b69ab31136
b69ab31137 userEvent.keyboard('{Escape}');
b69ab31138
b69ab31139 expect(
b69ab31140 within(screen.getByTestId('viewport-overlay-root')).queryByText(REFRESH_BUTTON_HOVER_TEXT),
b69ab31141 ).not.toBeInTheDocument();
b69ab31142 });
b69ab31143 });
b69ab31144});
b69ab31145
b69ab31146describe('tooltip', () => {
b69ab31147 function renderCustom(node: ReactNode) {
b69ab31148 render(
b69ab31149 <div className="isl-root">
b69ab31150 <ViewportOverlayRoot />
b69ab31151 {node}
b69ab31152 </div>,
b69ab31153 );
b69ab31154 }
b69ab31155
b69ab31156 describe('onDismiss', () => {
b69ab31157 it('calls onDismiss when hover leaves', () => {
b69ab31158 const onDismiss = jest.fn();
b69ab31159 renderCustom(
b69ab31160 <Tooltip trigger="hover" title="hi" onDismiss={onDismiss}>
b69ab31161 hover me
b69ab31162 </Tooltip>,
b69ab31163 );
b69ab31164 const tooltip = screen.getByText('hover me');
b69ab31165 userEvent.hover(tooltip);
b69ab31166 expect(onDismiss).not.toHaveBeenCalled();
b69ab31167 userEvent.unhover(tooltip);
b69ab31168 expect(onDismiss).toHaveBeenCalledTimes(1);
b69ab31169 });
b69ab31170
b69ab31171 it('calls onDismiss when pressing escape', () => {
b69ab31172 const onDismiss = jest.fn();
b69ab31173 renderCustom(
b69ab31174 <Tooltip trigger="hover" title="hi" onDismiss={onDismiss}>
b69ab31175 hover me
b69ab31176 </Tooltip>,
b69ab31177 );
b69ab31178 const tooltip = screen.getByText('hover me');
b69ab31179 userEvent.hover(tooltip);
b69ab31180 expect(onDismiss).not.toHaveBeenCalled();
b69ab31181 userEvent.keyboard('{Escape}');
b69ab31182 expect(onDismiss).toHaveBeenCalledTimes(1);
b69ab31183 });
b69ab31184
b69ab31185 it('calls onDismiss when clicking outside', () => {
b69ab31186 const onDismiss = jest.fn();
b69ab31187 renderCustom(
b69ab31188 <div>
b69ab31189 <div>something else</div>
b69ab31190 <Tooltip trigger="click" component={() => <div>hi</div>} onDismiss={onDismiss}>
b69ab31191 click me
b69ab31192 </Tooltip>
b69ab31193 </div>,
b69ab31194 );
b69ab31195 const tooltip = screen.getByText('click me');
b69ab31196 fireEvent.click(tooltip);
b69ab31197 expect(onDismiss).not.toHaveBeenCalled();
b69ab31198 const other = screen.getByText('something else');
b69ab31199 fireEvent.click(other);
b69ab31200 expect(onDismiss).toHaveBeenCalledTimes(1);
b69ab31201 });
b69ab31202
b69ab31203 it('title fields on click tooltips does not trigger onDismiss', () => {
b69ab31204 const onDismiss = jest.fn();
b69ab31205 renderCustom(
b69ab31206 <div>
b69ab31207 <div>something else</div>
b69ab31208 <Tooltip
b69ab31209 trigger="click"
b69ab31210 component={() => <div>hi</div>}
b69ab31211 title="hovered"
b69ab31212 onDismiss={onDismiss}>
b69ab31213 click me
b69ab31214 </Tooltip>
b69ab31215 </div>,
b69ab31216 );
b69ab31217 const tooltip = screen.getByText('click me');
b69ab31218 userEvent.hover(tooltip);
b69ab31219 expect(onDismiss).not.toHaveBeenCalled();
b69ab31220 userEvent.unhover(tooltip);
b69ab31221 expect(onDismiss).not.toHaveBeenCalled();
b69ab31222 });
b69ab31223
b69ab31224 it('dismiss prop in tooltip components calls onDismiss', () => {
b69ab31225 const onDismiss = jest.fn();
b69ab31226 renderCustom(
b69ab31227 <Tooltip
b69ab31228 trigger="click"
b69ab31229 component={dismiss => (
b69ab31230 <>
b69ab31231 <div>hi</div>
b69ab31232 <button onClick={dismiss}>my button</button>
b69ab31233 </>
b69ab31234 )}
b69ab31235 title="hovered"
b69ab31236 onDismiss={onDismiss}>
b69ab31237 click me
b69ab31238 </Tooltip>,
b69ab31239 );
b69ab31240 const tooltip = screen.getByText('click me');
b69ab31241 fireEvent.click(tooltip);
b69ab31242 expect(onDismiss).not.toHaveBeenCalled();
b69ab31243
b69ab31244 // clicking inside tooltip is fine
b69ab31245 const innerText = screen.getByText('hi');
b69ab31246 fireEvent.click(innerText);
b69ab31247 expect(onDismiss).not.toHaveBeenCalled();
b69ab31248
b69ab31249 // action that causes dismiss prop causes onDismiss
b69ab31250 const innerDismiss = screen.getByText('my button');
b69ab31251 fireEvent.click(innerDismiss);
b69ab31252 expect(onDismiss).toHaveBeenCalledTimes(1);
b69ab31253 });
b69ab31254 });
b69ab31255
b69ab31256 describe('groups', () => {
b69ab31257 it('dismisses other tooltips in the same group', () => {
b69ab31258 const content = (value: string) => {
b69ab31259 return () => <div>{value}</div>;
b69ab31260 };
b69ab31261 renderCustom(
b69ab31262 <div>
b69ab31263 <Tooltip trigger="click" group="test" component={content('Tooltip A')}>
b69ab31264 Button A
b69ab31265 </Tooltip>
b69ab31266 <Tooltip trigger="click" group="test" component={content('Tooltip B')}>
b69ab31267 Button B
b69ab31268 </Tooltip>
b69ab31269 </div>,
b69ab31270 );
b69ab31271 const a = screen.getByText('Button A');
b69ab31272 const b = screen.getByText('Button B');
b69ab31273 fireEvent.click(a);
b69ab31274 expect(screen.getByText('Tooltip A')).toBeInTheDocument();
b69ab31275 expect(screen.queryByText('Tooltip B')).not.toBeInTheDocument();
b69ab31276 fireEvent.click(b);
b69ab31277 expect(screen.queryByText('Tooltip A')).not.toBeInTheDocument();
b69ab31278 expect(screen.getByText('Tooltip B')).toBeInTheDocument();
b69ab31279 });
b69ab31280
b69ab31281 it('does not dismiss from other groups', () => {
b69ab31282 const content = (value: string) => {
b69ab31283 return () => <div>{value}</div>;
b69ab31284 };
b69ab31285 renderCustom(
b69ab31286 <div>
b69ab31287 <Tooltip trigger="click" group="test1" component={content('Tooltip A')}>
b69ab31288 Button A
b69ab31289 </Tooltip>
b69ab31290 <Tooltip trigger="click" group="test2" component={content('Tooltip B')}>
b69ab31291 Button B
b69ab31292 </Tooltip>
b69ab31293 </div>,
b69ab31294 );
b69ab31295 const a = screen.getByText('Button A');
b69ab31296 const b = screen.getByText('Button B');
b69ab31297 fireEvent.click(a);
b69ab31298 expect(screen.getByText('Tooltip A')).toBeInTheDocument();
b69ab31299 expect(screen.queryByText('Tooltip B')).not.toBeInTheDocument();
b69ab31300 fireEvent.click(b);
b69ab31301 expect(screen.getByText('Tooltip A')).toBeInTheDocument();
b69ab31302 expect(screen.getByText('Tooltip B')).toBeInTheDocument();
b69ab31303 });
b69ab31304 });
b69ab31305});