Просмотр исходного кода

ref(ui): Use DropdownMenuV2 for dashboard widget dropdown (#31688)

* ref(ui): Use DropdownMenuV2 for dashboard widget dropdown

* fix(test): Update tests related to dashboard widget dropdowns

Necessary given the new implementation of DropdownMenuV2.

* fix(acceptance): Update acceptance tests for Dashboards

* ref(ui): Limit query card dropdown height

* ref(test): Use `findBy` instead of `await tick()` in issueWidgetCard

* style: Merge similar menuOptions in widgetCardContextMenu

* ref(test): Use `findBy` instead of `await tick()` in widgetCard test

* ref(test): Remove `data-test-href` attribute

Instead, check for href values by clicking on the link and checking for route changes.
Vu Luong 3 лет назад
Родитель
Сommit
3bff398094

+ 100 - 91
static/app/views/dashboardsV2/widgetCard/widgetCardContextMenu.tsx

@@ -1,12 +1,19 @@
-import * as React from 'react';
 import styled from '@emotion/styled';
 import * as qs from 'query-string';
 
 import {openDashboardWidgetQuerySelectorModal} from 'sentry/actionCreators/modal';
 import {parseArithmetic} from 'sentry/components/arithmeticInput/parser';
-import Confirm from 'sentry/components/confirm';
-import Link from 'sentry/components/links/link';
-import MenuItem from 'sentry/components/menuItem';
+import {openConfirmModal} from 'sentry/components/confirm';
+import DropdownMenuControlV2 from 'sentry/components/dropdownMenuControlV2';
+import {MenuItemProps} from 'sentry/components/dropdownMenuItemV2';
+import {
+  IconCopy,
+  IconDelete,
+  IconEdit,
+  IconEllipsis,
+  IconIssues,
+  IconTelescope,
+} from 'sentry/icons';
 import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Organization, PageFilters} from 'sentry/types';
@@ -17,7 +24,6 @@ import {DisplayModes} from 'sentry/utils/discover/types';
 import {eventViewFromWidget} from 'sentry/views/dashboardsV2/utils';
 import {DisplayType} from 'sentry/views/dashboardsV2/widgetBuilder/utils';
 
-import ContextMenu from '../contextMenu';
 import {Widget, WidgetType} from '../types';
 
 type Props = {
@@ -47,16 +53,29 @@ function WidgetCardContextMenu({
     return null;
   }
 
-  const menuOptions: React.ReactNode[] = [];
+  const menuOptions: MenuItemProps[] = [];
+  const disabledKeys: string[] = [];
 
   if (isPreview) {
     return (
       <ContextWrapper>
-        <ContextMenu>
-          <PreviewMessage>
-            {t('This is a preview only. To edit, you must add this dashboard.')}
-          </PreviewMessage>
-        </ContextMenu>
+        <DropdownMenuControlV2
+          items={[
+            {
+              key: 'preview',
+              label: t('This is a preview only. To edit, you must add this dashboard.'),
+            },
+          ]}
+          triggerProps={{
+            'aria-label': t('Widget actions'),
+            size: 'xsmall',
+            borderless: true,
+            showChevron: false,
+            icon: <IconEllipsis direction="down" size="sm" />,
+          }}
+          placement="bottom right"
+          disabledKeys={['preview']}
+        />
       </ContextWrapper>
     );
   }
@@ -118,38 +137,28 @@ function WidgetCardContextMenu({
       const discoverPath = `${discoverLocation.pathname}?${qs.stringify({
         ...discoverLocation.query,
       })}`;
-      if (widget.queries.length === 1) {
-        menuOptions.push(
-          <Link
-            key="open-discover-link"
-            to={discoverPath}
-            onClick={() => {
-              trackAdvancedAnalyticsEvent('dashboards_views.open_in_discover.opened', {
-                organization,
-                widget_type: widget.displayType,
-              });
-            }}
-          >
-            <StyledMenuItem key="open-discover">{t('Open in Discover')}</StyledMenuItem>
-          </Link>
-        );
-      } else {
-        menuOptions.push(
-          <StyledMenuItem
-            key="open-discover"
-            onClick={event => {
-              event.preventDefault();
-              trackAdvancedAnalyticsEvent('dashboards_views.query_selector.opened', {
-                organization,
-                widget_type: widget.displayType,
-              });
-              openDashboardWidgetQuerySelectorModal({organization, widget});
-            }}
-          >
-            {t('Open in Discover')}
-          </StyledMenuItem>
-        );
-      }
+
+      menuOptions.push({
+        key: 'open-in-discover',
+        label: t('Open in Discover'),
+        leadingItems: <IconTelescope />,
+        to: widget.queries.length === 1 ? discoverPath : undefined,
+        onAction: () => {
+          if (widget.queries.length === 1) {
+            trackAdvancedAnalyticsEvent('dashboards_views.open_in_discover.opened', {
+              organization,
+              widget_type: widget.displayType,
+            });
+            return;
+          }
+
+          trackAdvancedAnalyticsEvent('dashboards_views.query_selector.opened', {
+            organization,
+            widget_type: widget.displayType,
+          });
+          openDashboardWidgetQuerySelectorModal({organization, widget});
+        },
+      });
     }
   }
 
@@ -165,43 +174,42 @@ function WidgetCardContextMenu({
       ...datetime,
     })}`;
 
-    menuOptions.push(
-      <Link to={issuesLocation} key="open-issues-link">
-        <StyledMenuItem key="open-issues">{t('Open in Issues')}</StyledMenuItem>
-      </Link>
-    );
+    menuOptions.push({
+      key: 'open-in-issues',
+      label: t('Open in Issues'),
+      leadingItems: <IconIssues />,
+      to: issuesLocation,
+    });
   }
 
   if (organization.features.includes('dashboards-edit')) {
-    menuOptions.push(
-      <StyledMenuItem
-        key="duplicate-widget"
-        data-test-id="duplicate-widget"
-        onSelect={onDuplicate}
-        disabled={widgetLimitReached}
-      >
-        {t('Duplicate Widget')}
-      </StyledMenuItem>
-    );
-
-    menuOptions.push(
-      <StyledMenuItem key="edit-widget" data-test-id="edit-widget" onSelect={onEdit}>
-        {t('Edit Widget')}
-      </StyledMenuItem>
-    );
-
-    menuOptions.push(
-      <Confirm
-        key="delete-widget"
-        priority="danger"
-        message={t('Are you sure you want to delete this widget?')}
-        onConfirm={onDelete}
-      >
-        <StyledMenuItem data-test-id="delete-widget" danger>
-          {t('Delete Widget')}
-        </StyledMenuItem>
-      </Confirm>
-    );
+    menuOptions.push({
+      key: 'duplicate-widget',
+      label: t('Duplicate Widget'),
+      leadingItems: <IconCopy />,
+      onAction: () => onDuplicate(),
+    });
+    widgetLimitReached && disabledKeys.push('duplicate-widget');
+
+    menuOptions.push({
+      key: 'edit-widget',
+      label: t('Edit Widget'),
+      leadingItems: <IconEdit />,
+      onAction: () => onEdit(),
+    });
+
+    menuOptions.push({
+      key: 'delete-widget',
+      label: t('Delete Widget'),
+      leadingItems: <IconDelete />,
+      onAction: () => {
+        openConfirmModal({
+          message: t('Are you sure you want to delete this widget?'),
+          priority: 'danger',
+          onConfirm: () => onDelete(),
+        });
+      },
+    });
   }
 
   if (!menuOptions.length) {
@@ -210,7 +218,18 @@ function WidgetCardContextMenu({
 
   return (
     <ContextWrapper>
-      <ContextMenu>{menuOptions}</ContextMenu>
+      <DropdownMenuControlV2
+        items={menuOptions}
+        triggerProps={{
+          'aria-label': t('Widget actions'),
+          size: 'xsmall',
+          borderless: true,
+          showChevron: false,
+          icon: <IconEllipsis direction="down" size="sm" />,
+        }}
+        placement="bottom right"
+        disabledKeys={disabledKeys}
+      />
     </ContextWrapper>
   );
 }
@@ -218,18 +237,8 @@ function WidgetCardContextMenu({
 export default WidgetCardContextMenu;
 
 const ContextWrapper = styled('div')`
+  display: flex;
+  align-items: center;
+  height: ${space(3)};
   margin-left: ${space(1)};
 `;
-
-const StyledMenuItem = styled(MenuItem)<{danger?: boolean}>`
-  white-space: nowrap;
-  color: ${p => (p.danger ? p.theme.red300 : p.theme.textColor)};
-  :hover {
-    color: ${p => (p.danger ? p.theme.red300 : p.theme.textColor)};
-  }
-`;
-
-const PreviewMessage = styled('span')`
-  padding: ${space(1)};
-  display: block;
-`;

+ 14 - 14
tests/acceptance/test_organization_dashboards.py

@@ -118,11 +118,11 @@ class OrganizationDashboardsAcceptanceTest(AcceptanceTestCase):
         with self.feature(FEATURE_NAMES + EDIT_FEATURE):
             self.page.visit_dashboard_detail()
 
-            self.browser.element('[data-test-id="context-menu"]').click()
+            self.browser.element('[aria-haspopup="true"]').click()
             self.browser.element('[data-test-id="duplicate-widget"]').click()
             self.page.wait_until_loaded()
 
-            self.browser.elements('[data-test-id="context-menu"]')[0].click()
+            self.browser.element('[aria-haspopup="true"]').click()
             self.browser.element('[data-test-id="duplicate-widget"]').click()
             self.page.wait_until_loaded()
 
@@ -132,7 +132,7 @@ class OrganizationDashboardsAcceptanceTest(AcceptanceTestCase):
         with self.feature(FEATURE_NAMES + EDIT_FEATURE):
             self.page.visit_dashboard_detail()
 
-            self.browser.element('[data-test-id="context-menu"]').click()
+            self.browser.element('[aria-haspopup="true"]').click()
             self.browser.element('[data-test-id="delete-widget"]').click()
             self.browser.element('[data-test-id="confirm-button"]').click()
 
@@ -476,11 +476,11 @@ class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
         with self.feature(FEATURE_NAMES + EDIT_FEATURE):
             self.page.visit_dashboard_detail()
 
-            self.browser.element('[data-test-id="context-menu"]').click()
+            self.browser.element('[aria-haspopup="true"]').click()
             self.browser.element('[data-test-id="duplicate-widget"]').click()
             self.page.wait_until_loaded()
 
-            self.browser.elements('[data-test-id="context-menu"]')[0].click()
+            self.browser.element('[aria-haspopup="true"]').click()
             self.browser.element('[data-test-id="duplicate-widget"]').click()
             self.page.wait_until_loaded()
 
@@ -507,7 +507,7 @@ class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
         with self.feature(FEATURE_NAMES + EDIT_FEATURE):
             self.page.visit_dashboard_detail()
 
-            self.browser.element('[data-test-id="context-menu"]').click()
+            self.browser.element('[aria-haspopup="true"]').click()
             self.browser.element('[data-test-id="delete-widget"]').click()
             self.browser.element('[data-test-id="confirm-button"]').click()
 
@@ -643,8 +643,8 @@ class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
         ):
             self.page.visit_dashboard_detail()
 
-            context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
-            context_menu_icon.click()
+            dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
+            dropdown_trigger.click()
 
             delete_widget_menu_item = self.browser.element('[data-test-id="delete-widget"]')
             delete_widget_menu_item.click()
@@ -695,8 +695,8 @@ class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
             self.page.visit_dashboard_detail()
 
             # Open edit modal for first widget
-            context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
-            context_menu_icon.click()
+            dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
+            dropdown_trigger.click()
             edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
             edit_widget_menu_item.click()
 
@@ -743,8 +743,8 @@ class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
             self.page.visit_dashboard_detail()
 
             # Open edit modal for first widget
-            context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
-            context_menu_icon.click()
+            dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
+            dropdown_trigger.click()
             edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
             edit_widget_menu_item.click()
 
@@ -791,8 +791,8 @@ class OrganizationDashboardLayoutAcceptanceTest(AcceptanceTestCase):
             self.page.visit_dashboard_detail()
 
             # Open edit modal for first widget
-            context_menu_icon = self.browser.element('[data-test-id="context-menu"]')
-            context_menu_icon.click()
+            dropdown_trigger = self.browser.element('[aria-haspopup="true"]')
+            dropdown_trigger.click()
             edit_widget_menu_item = self.browser.element('[data-test-id="edit-widget"]')
             edit_widget_menu_item.click()
 

+ 1 - 1
tests/js/sentry-test/dropdownMenu.tsx

@@ -46,7 +46,7 @@ export async function selectDropdownMenuItem({
   wrapper,
   itemKey,
   specifiers,
-  triggerSelector = 'DropdownTrigger',
+  triggerSelector = 'Button',
 }: SelectDropdownItemProps) {
   /**
    * Returns a ReactWrapper which we'll use to find the

+ 1 - 0
tests/js/spec/components/actions/resolve.spec.jsx

@@ -210,6 +210,7 @@ describe('ResolveActions', function () {
     await selectDropdownMenuItem({
       wrapper,
       itemKey: 'another-release',
+      triggerSelector: 'DropdownTrigger',
     });
 
     expect(wrapper.find('CustomResolutionModal Select').prop('options')).toEqual([

+ 13 - 16
tests/js/spec/views/dashboardsV2/detail.spec.jsx

@@ -1,10 +1,12 @@
 import {browserHistory} from 'react-router';
 
 import {createListeners} from 'sentry-test/createListeners';
+import {selectDropdownMenuItem} from 'sentry-test/dropdownMenu';
 import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
 import {initializeOrg} from 'sentry-test/initializeOrg';
 import {mountGlobalModal} from 'sentry-test/modal';
 import {act} from 'sentry-test/reactTestingLibrary';
+import {triggerPress} from 'sentry-test/utils';
 
 import * as modals from 'sentry/actionCreators/modal';
 import ProjectsStore from 'sentry/stores/projectsStore';
@@ -722,16 +724,15 @@ describe('Dashboards > Detail', function () {
       ).toEqual(true);
       expect(wrapper.find('Controls Tooltip').prop('disabled')).toBe(false);
 
-      const card2 = wrapper.find('WidgetCard').first();
-      card2.find('DropdownMenu MoreOptions svg').simulate('click');
+      await act(async () => {
+        triggerPress(wrapper.first().find('MenuControlWrap Button').first());
 
-      card2.update();
-      wrapper.update();
+        await tick();
+        wrapper.update();
+      });
 
       expect(
-        wrapper
-          .find(`DropdownMenu MenuItem[data-test-id="duplicate-widget"] MenuTarget`)
-          .props().disabled
+        wrapper.find(`MenuItemWrap[data-test-id="duplicate-widget"]`).props().isDisabled
       ).toEqual(true);
     });
 
@@ -750,15 +751,11 @@ describe('Dashboards > Detail', function () {
 
       expect(wrapper.find('WidgetCard')).toHaveLength(3);
 
-      const card = wrapper.find('WidgetCard').first();
-      card.find('DropdownMenu MoreOptions svg').simulate('click');
-
-      card.update();
-      wrapper.update();
-
-      wrapper
-        .find(`DropdownMenu MenuItem[data-test-id="edit-widget"] MenuTarget`)
-        .simulate('click');
+      await selectDropdownMenuItem({
+        wrapper,
+        specifiers: {prefix: 'WidgetCard', first: true},
+        itemKey: 'edit-widget',
+      });
 
       expect(openEditModal).toHaveBeenCalledTimes(1);
       expect(openEditModal).toHaveBeenCalledWith(

+ 17 - 23
tests/js/spec/views/dashboardsV2/issueWidgetCard.spec.tsx

@@ -8,7 +8,7 @@ import WidgetCard from 'sentry/views/dashboardsV2/widgetCard';
 import {IssueSortOptions} from 'sentry/views/issueList/utils';
 
 describe('Dashboards > IssueWidgetCard', function () {
-  const initialData = initializeOrg({
+  const {router, organization, routerContext} = initializeOrg({
     organization: TestStubs.Organization({
       features: ['dashboards-edit'],
     }),
@@ -79,7 +79,7 @@ describe('Dashboards > IssueWidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={widget}
         selection={selection}
         isEditing={false}
@@ -94,9 +94,7 @@ describe('Dashboards > IssueWidgetCard', function () {
       />
     );
 
-    await tick();
-
-    expect(screen.getByText('Issues')).toBeInTheDocument();
+    expect(await screen.findByText('Issues')).toBeInTheDocument();
     expect(screen.getByText('assignee')).toBeInTheDocument();
     expect(screen.getByText('title')).toBeInTheDocument();
     expect(screen.getByText('issue')).toBeInTheDocument();
@@ -114,7 +112,7 @@ describe('Dashboards > IssueWidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={widget}
         selection={selection}
         isEditing={false}
@@ -126,16 +124,18 @@ describe('Dashboards > IssueWidgetCard', function () {
         currentWidgetDragging={false}
         showContextMenu
         widgetLimitReached={false}
-      />
+      />,
+      {context: routerContext}
     );
 
-    await tick();
+    userEvent.click(await screen.findByLabelText('Widget actions'));
+    expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
 
-    userEvent.click(screen.getByTestId('context-menu'));
-    expect(screen.getByText('Open in Issues').closest('a')?.href).toContain(
+    expect(screen.getByText('Open in Issues')).toBeInTheDocument();
+    userEvent.click(screen.getByRole('menuitemradio', {name: 'Open in Issues'}));
+    expect(router.push).toHaveBeenCalledWith(
       '/organizations/org-slug/issues/?query=event.type%3Adefault&sort=freq&statsPeriod=14d'
     );
-    expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
   });
 
   it('calls onDuplicate when Duplicate Widget is clicked', async function () {
@@ -143,7 +143,7 @@ describe('Dashboards > IssueWidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={widget}
         selection={selection}
         isEditing={false}
@@ -158,9 +158,7 @@ describe('Dashboards > IssueWidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
     userEvent.click(screen.getByText('Duplicate Widget'));
     expect(mock).toHaveBeenCalledTimes(1);
@@ -171,7 +169,7 @@ describe('Dashboards > IssueWidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={widget}
         selection={selection}
         isEditing={false}
@@ -186,9 +184,7 @@ describe('Dashboards > IssueWidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
     userEvent.click(screen.getByText('Duplicate Widget'));
     expect(mock).toHaveBeenCalledTimes(0);
@@ -199,7 +195,7 @@ describe('Dashboards > IssueWidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...widget,
           queries: [
@@ -222,9 +218,7 @@ describe('Dashboards > IssueWidgetCard', function () {
       />
     );
 
-    await tick();
-
-    expect(screen.getByText('Lifetime Events')).toBeInTheDocument();
+    expect(await screen.findByText('Lifetime Events')).toBeInTheDocument();
     expect(screen.getByText('Lifetime Users')).toBeInTheDocument();
   });
 });

+ 40 - 54
tests/js/spec/views/dashboardsV2/widgetCard.spec.tsx

@@ -13,7 +13,7 @@ jest.mock('sentry/components/charts/simpleTableChart');
 jest.mock('sentry/views/dashboardsV2/widgetCard/metricsWidgetQueries');
 
 describe('Dashboards > WidgetCard', function () {
-  const initialData = initializeOrg({
+  const {router, organization, routerContext} = initializeOrg({
     organization: TestStubs.Organization({
       features: ['dashboards-edit', 'discover-basic'],
       projects: [TestStubs.Project()],
@@ -82,7 +82,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={multipleQueryWidget}
         selection={selection}
         isEditing={false}
@@ -97,13 +97,11 @@ describe('Dashboards > WidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Open in Discover')).toBeInTheDocument();
     userEvent.click(screen.getByText('Open in Discover'));
     expect(spy).toHaveBeenCalledWith({
-      organization: initialData.organization,
+      organization,
       widget: multipleQueryWidget,
     });
   });
@@ -112,7 +110,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{...multipleQueryWidget, queries: [multipleQueryWidget.queries[0]]}}
         selection={selection}
         isEditing={false}
@@ -124,15 +122,14 @@ describe('Dashboards > WidgetCard', function () {
         currentWidgetDragging={false}
         showContextMenu
         widgetLimitReached={false}
-      />
+      />,
+      {context: routerContext}
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Open in Discover')).toBeInTheDocument();
-    expect(screen.getByText('Open in Discover').closest('a')).toHaveAttribute(
-      'href',
+    userEvent.click(screen.getByRole('menuitemradio', {name: 'Open in Discover'}));
+    expect(router.push).toHaveBeenCalledWith(
       '/organizations/org-slug/discover/results/?environment=prod&field=count%28%29&field=failure_count%28%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=count%28%29&yAxis=failure_count%28%29'
     );
   });
@@ -141,7 +138,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.WORLD_MAP,
@@ -157,15 +154,14 @@ describe('Dashboards > WidgetCard', function () {
         currentWidgetDragging={false}
         showContextMenu
         widgetLimitReached={false}
-      />
+      />,
+      {context: routerContext}
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Open in Discover')).toBeInTheDocument();
-    expect(screen.getByText('Open in Discover').closest('a')).toHaveAttribute(
-      'href',
+    userEvent.click(screen.getByRole('menuitemradio', {name: 'Open in Discover'}));
+    expect(router.push).toHaveBeenCalledWith(
       '/organizations/org-slug/discover/results/?display=worldmap&environment=prod&field=geo.country_code&field=count%28%29&name=Errors&project=1&query=event.type%3Aerror%20has%3Ageo.country_code&statsPeriod=14d&yAxis=count%28%29'
     );
   });
@@ -174,7 +170,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           queries: [
@@ -196,15 +192,14 @@ describe('Dashboards > WidgetCard', function () {
         currentWidgetDragging={false}
         showContextMenu
         widgetLimitReached={false}
-      />
+      />,
+      {context: routerContext}
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Open in Discover')).toBeInTheDocument();
-    expect(screen.getByText('Open in Discover').closest('a')).toHaveAttribute(
-      'href',
+    userEvent.click(screen.getByRole('menuitemradio', {name: 'Open in Discover'}));
+    expect(router.push).toHaveBeenCalledWith(
       '/organizations/org-slug/discover/results/?environment=prod&field=count_if%28transaction.duration%2Cequals%2C300%29&field=failure_count%28%29&field=count%28%29&field=equation%7C%28count%28%29%20%2B%20failure_count%28%29%29%20%2F%20count_if%28transaction.duration%2Cequals%2C300%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=equation%7C%28count%28%29%20%2B%20failure_count%28%29%29%20%2F%20count_if%28transaction.duration%2Cequals%2C300%29'
     );
   });
@@ -213,7 +208,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.TOP_N,
@@ -231,15 +226,14 @@ describe('Dashboards > WidgetCard', function () {
         currentWidgetDragging={false}
         showContextMenu
         widgetLimitReached={false}
-      />
+      />,
+      {context: routerContext}
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Open in Discover')).toBeInTheDocument();
-    expect(screen.getByText('Open in Discover').closest('a')).toHaveAttribute(
-      'href',
+    userEvent.click(screen.getByRole('menuitemradio', {name: 'Open in Discover'}));
+    expect(router.push).toHaveBeenCalledWith(
       '/organizations/org-slug/discover/results/?display=top5&environment=prod&field=transaction&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=count%28%29'
     );
   });
@@ -249,7 +243,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.WORLD_MAP,
@@ -268,9 +262,7 @@ describe('Dashboards > WidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
     userEvent.click(screen.getByText('Duplicate Widget'));
     expect(mock).toHaveBeenCalledTimes(1);
@@ -281,7 +273,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.WORLD_MAP,
@@ -300,9 +292,7 @@ describe('Dashboards > WidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
     userEvent.click(screen.getByText('Duplicate Widget'));
     expect(mock).toHaveBeenCalledTimes(0);
@@ -313,7 +303,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.WORLD_MAP,
@@ -332,9 +322,7 @@ describe('Dashboards > WidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Edit Widget')).toBeInTheDocument();
     userEvent.click(screen.getByText('Edit Widget'));
     expect(mock).toHaveBeenCalledTimes(1);
@@ -345,7 +333,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.WORLD_MAP,
@@ -364,9 +352,7 @@ describe('Dashboards > WidgetCard', function () {
       />
     );
 
-    await tick();
-
-    userEvent.click(screen.getByTestId('context-menu'));
+    userEvent.click(await screen.findByLabelText('Widget actions'));
     expect(screen.getByText('Delete Widget')).toBeInTheDocument();
     userEvent.click(screen.getByText('Delete Widget'));
     // Confirm Modal
@@ -383,7 +369,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.TABLE,
@@ -418,7 +404,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={{
           ...multipleQueryWidget,
           displayType: DisplayType.TABLE,
@@ -465,7 +451,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={tableWidget}
         selection={selection}
         isEditing={false}
@@ -498,7 +484,7 @@ describe('Dashboards > WidgetCard', function () {
     mountWithTheme(
       <WidgetCard
         api={api}
-        organization={initialData.organization}
+        organization={organization}
         widget={widget}
         selection={selection}
         isEditing={false}

+ 1 - 0
tests/js/spec/views/issueList/actions.spec.jsx

@@ -229,6 +229,7 @@ describe('IssueListActions', function () {
         await selectDropdownMenuItem({
           wrapper,
           specifiers: {prefix: 'IgnoreActions'},
+          triggerSelector: 'DropdownTrigger',
           itemKey: ['until-affect', 'until-affect-custom'],
         });