Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion static/app/views/alerts/alertBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function AlertBadge({status, hideText = false, isIssue}: Props) {
}

return (
<Wrapper displayFlex={!hideText}>
<Wrapper data-test-id="alert-badge" displayFlex={!hideText}>
<AlertIconWrapper color={color} icon={Icon}>
<Icon color="white" />
</AlertIconWrapper>
Expand Down
184 changes: 93 additions & 91 deletions tests/js/spec/views/alerts/rules/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {mountWithTheme} from 'sentry-test/enzyme';
import {initializeOrg} from 'sentry-test/initializeOrg';
import {act} from 'sentry-test/reactTestingLibrary';
import {act, fireEvent, mountWithTheme, screen} from 'sentry-test/reactTestingLibrary';

import OrganizationStore from 'app/stores/organizationStore';
import ProjectsStore from 'app/stores/projectsStore';
import {trackAnalyticsEvent} from 'app/utils/analytics';
import AlertRulesList from 'app/views/alerts/rules';
Expand All @@ -13,22 +13,19 @@ describe('OrganizationRuleList', () => {
const {routerContext, organization, router} = initializeOrg();
let rulesMock;
let projectMock;
let wrapper;

const createWrapper = async props => {
wrapper = mountWithTheme(
<AlertRulesList
organization={organization}
params={{orgId: organization.slug}}
location={{query: {}, search: ''}}
router={router}
{...props}
/>,
routerContext
);
await tick();
wrapper.update();
};

const getComponent = props => (
<AlertRulesList
organization={organization}
params={{orgId: organization.slug}}
location={{query: {}, search: ''}}
router={router}
{...props}
/>
);

const createWrapper = props =>
mountWithTheme(getComponent(props), {context: routerContext});

beforeEach(() => {
rulesMock = MockApiClient.addMockResponse({
Expand Down Expand Up @@ -59,35 +56,30 @@ describe('OrganizationRuleList', () => {
body: [TestStubs.Project({slug: 'earth', platform: 'javascript'})],
});

act(() => OrganizationStore.onUpdate(organization, {replace: true}));
act(() => ProjectsStore.loadInitialData([]));
});

afterEach(() => {
wrapper.unmount();
act(() => ProjectsStore.reset());
MockApiClient.clearMockResponses();
trackAnalyticsEvent.mockClear();
});

it('displays list', async () => {
await createWrapper();
createWrapper();

const row = wrapper.find('RuleListRow').at(0);
expect(row.find('AlertName').text()).toBe('First Issue Alert');

// GlobalSelectionHeader loads projects + the Projects render-prop
// component to load projects for all rows.
expect(projectMock).toHaveBeenCalledTimes(2);
expect(await screen.findByText('First Issue Alert')).toBeInTheDocument();

expect(projectMock).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({
query: expect.objectContaining({query: 'slug:earth'}),
})
);
expect(wrapper.find('IdBadge').at(0).prop('project')).toMatchObject({
slug: 'earth',
});

expect(screen.getAllByTestId('badge-display-name')[0]).toHaveTextContent('earth');

expect(trackAnalyticsEvent).toHaveBeenCalledWith({
eventKey: 'alert_rules.viewed',
eventName: 'Alert Rules: Viewed',
Expand All @@ -102,59 +94,70 @@ describe('OrganizationRuleList', () => {
body: [],
});

await createWrapper();
createWrapper();

expect(rulesMock).toHaveBeenCalledTimes(0);
expect(
await screen.findByText('No alert rules found for the current query.')
).toBeInTheDocument();

expect(wrapper.find('PanelItem')).toHaveLength(0);
expect(wrapper.text()).toContain('No alert rules found for the current query');
expect(rulesMock).toHaveBeenCalledTimes(0);
});

it('sorts by date created', async () => {
await createWrapper();
const {rerender} = createWrapper();

expect(wrapper.find('IconArrow').at(0).prop('direction')).toBe('down');
// The created column is not used for sorting
expect(await screen.findByText('Created')).toHaveAttribute('aria-sort', 'none');

wrapper.setProps({
location: {query: {asc: '1'}, search: '?asc=1`'},
});
// Sort by created (date_added)
rerender(
getComponent({
location: {
query: {asc: '1', sort: 'date_added'},
search: '?asc=1&sort=date_added`',
},
})
);

expect(wrapper.find('IconArrow').at(0).prop('direction')).toBe('up');
expect(await screen.findByText('Created')).toHaveAttribute('aria-sort', 'ascending');

expect(rulesMock).toHaveBeenCalledTimes(2);

expect(rulesMock).toHaveBeenCalledWith(
'/organizations/org-slug/combined-rules/',
expect.objectContaining({query: expect.objectContaining({asc: '1'})})
expect.objectContaining({
query: expect.objectContaining({asc: '1'}),
})
);
});

it('sorts by name', async () => {
await createWrapper();
const {rerender} = createWrapper();

const nameHeader = wrapper.find('StyledSortLink').first();
expect(nameHeader.text()).toContain('Alert Rule');
expect(nameHeader.props().to).toEqual(
expect.objectContaining({
query: {
sort: 'name',
asc: '1',
team: ['myteams', 'unassigned'],
expand: ['latestIncident'],
// The name column is not used for sorting
expect(await screen.findByText('Alert Rule')).toHaveAttribute('aria-sort', 'none');

// Sort by the name column
rerender(
getComponent({
location: {
query: {asc: '1', sort: 'name'},
search: '?asc=1&sort=name`',
},
})
);

wrapper.setProps({
location: {query: {sort: 'name'}, search: '?asc=1&sort=name`'},
});
expect(await screen.findByText('Alert Rule')).toHaveAttribute(
'aria-sort',
'ascending'
);

expect(rulesMock).toHaveBeenCalledTimes(2);

expect(wrapper.find('StyledSortLink').first().props().to).toEqual(
expect(rulesMock).toHaveBeenCalledWith(
'/organizations/org-slug/combined-rules/',
expect.objectContaining({
query: {
sort: 'name',
asc: '1',
},
query: expect.objectContaining({sort: 'name', asc: '1'}),
})
);
});
Expand All @@ -165,29 +168,24 @@ describe('OrganizationRuleList', () => {
access: [],
};

await createWrapper({organization: noAccessOrg});
const {rerender} = createWrapper({organization: noAccessOrg});

const addButton = wrapper.find('button[aria-label="Create Alert Rule"]');
expect(addButton.props()['aria-disabled']).toBe(true);
wrapper.unmount();
expect(await screen.findByLabelText('Create Alert Rule')).toBeDisabled();

// Enabled with access
await createWrapper();

const addLink = wrapper.find('button[aria-label="Create Alert Rule"]');
expect(addLink.props()['aria-disabled']).toBe(false);
rerender(getComponent());
expect(await screen.findByLabelText('Create Alert Rule')).toBeEnabled();
});

it('searches by name', async () => {
await createWrapper();
expect(wrapper.find('StyledSearchBar').exists()).toBe(true);
createWrapper();

const search = await screen.findByPlaceholderText('Search by name');
expect(search).toBeInTheDocument();

const testQuery = 'test name';
wrapper
.find('StyledSearchBar')
.find('input')
.simulate('change', {target: {value: testQuery}})
.simulate('submit', {preventDefault() {}});
fireEvent.change(search, {target: {value: testQuery}});
fireEvent.submit(search);

expect(router.push).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -202,16 +200,18 @@ describe('OrganizationRuleList', () => {
});

it('uses empty team query parameter when removing all teams', async () => {
await createWrapper();
const {rerender} = createWrapper();

expect(await screen.findByText('First Issue Alert')).toBeInTheDocument();

rerender(
getComponent({location: {query: {team: 'myteams'}, search: '?team=myteams`'}})
);

fireEvent.click(await screen.findByTestId('filter-button'));

wrapper.setProps({
location: {query: {team: 'myteams'}, search: '?team=myteams`'},
});
wrapper.find('Button[data-test-id="filter-button"]').simulate('click');
// Uncheck myteams
const myTeamsItem = wrapper.find('Filter').find('ListItem').at(0);
expect(myTeamsItem.text()).toBe('My Teams');
myTeamsItem.simulate('click');
fireEvent.click(await screen.findByText('My Teams'));

expect(router.push).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -225,19 +225,21 @@ describe('OrganizationRuleList', () => {
});

it('displays alert status', async () => {
await createWrapper({organization});
let row = wrapper.find('RuleListRow').at(1);
expect(row.find('AlertNameAndStatus').text()).toContain('My Incident Rule');
expect(row.find('AlertNameAndStatus').text()).toContain('Triggered');
expect(row.find('TriggerText').text()).toBe('Above 70');

row = wrapper.find('RuleListRow').at(2);
expect(row.find('TriggerText').text()).toBe('Below 36');
expect(wrapper.find('AlertIconWrapper').exists()).toBe(true);
createWrapper();
const rules = await screen.findAllByText('My Incident Rule');

expect(rules[0]).toBeInTheDocument();

expect(screen.getByText('Triggered')).toBeInTheDocument();
expect(screen.getByText('Above 70')).toBeInTheDocument();
expect(screen.getByText('Below 36')).toBeInTheDocument();
expect(screen.getAllByTestId('alert-badge')[0]).toBeInTheDocument();
});

it('sorts by alert rule', async () => {
await createWrapper({organization});
createWrapper({organization});

expect(await screen.findByText('First Issue Alert')).toBeInTheDocument();

expect(rulesMock).toHaveBeenCalledWith(
'/organizations/org-slug/combined-rules/',
Expand Down