Merge pull request #14651 from backstage/freben/tests

add some eslint rules for testing-library use in tests
This commit is contained in:
Fredrik Adelöw
2022-11-18 15:08:22 +01:00
committed by GitHub
31 changed files with 236 additions and 134 deletions
@@ -46,7 +46,7 @@ describe('BitriseArtifactsComponent', () => {
const rendered = renderComponent();
const btn = await rendered.findByTestId('btn');
expect(await rendered.queryByText('VISIBLE')).not.toBeInTheDocument();
expect(rendered.queryByText('VISIBLE')).not.toBeInTheDocument();
btn.click();
@@ -13,64 +13,65 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';
import user from '@testing-library/user-event';
import React from 'react';
import { MaxDepthFilter } from './MaxDepthFilter';
describe('<MaxDepthFilter/>', () => {
test('should display current value', () => {
const { getByLabelText } = render(
<MaxDepthFilter value={5} onChange={() => {}} />,
);
render(<MaxDepthFilter value={5} onChange={() => {}} />);
expect(getByLabelText('maxp')).toBeInTheDocument();
expect(getByLabelText('maxp')).toHaveValue(5);
expect(screen.getByLabelText('maxp')).toBeInTheDocument();
expect(screen.getByLabelText('maxp')).toHaveValue(5);
});
test('should display infinite if non finite', () => {
const { getByPlaceholderText, getByLabelText } = render(
render(
<MaxDepthFilter value={Number.POSITIVE_INFINITY} onChange={() => {}} />,
);
expect(getByPlaceholderText(/Infinite/)).toBeInTheDocument();
expect(getByLabelText('maxp')).toHaveValue(null);
expect(screen.getByPlaceholderText(/Infinite/)).toBeInTheDocument();
expect(screen.getByLabelText('maxp')).toHaveValue(null);
});
test('should clear max depth', async () => {
const onChange = jest.fn();
const { getByLabelText } = render(
<MaxDepthFilter value={10} onChange={onChange} />,
);
render(<MaxDepthFilter value={10} onChange={onChange} />);
await userEvent.click(getByLabelText('clear max depth'));
expect(onChange).not.toHaveBeenCalled();
await user.click(screen.getByLabelText('clear max depth'));
expect(onChange).toHaveBeenCalledWith(Number.POSITIVE_INFINITY);
});
test('should set max depth to undefined if below one', async () => {
const onChange = jest.fn();
const { getByLabelText } = render(
<MaxDepthFilter value={1} onChange={onChange} />,
);
render(<MaxDepthFilter value={1} onChange={onChange} />);
await userEvent.clear(getByLabelText('maxp'));
await userEvent.type(getByLabelText('maxp'), '0');
await user.clear(screen.getByLabelText('maxp'));
await user.type(screen.getByLabelText('maxp'), '0');
expect(onChange).toHaveBeenCalledWith(Number.POSITIVE_INFINITY);
});
test('should select direction', async () => {
const onChange = jest.fn();
const { getByLabelText } = render(
<MaxDepthFilter value={5} onChange={onChange} />,
let value = 5;
render(
<MaxDepthFilter
value={value}
onChange={v => {
value = v;
}}
/>,
);
expect(getByLabelText('maxp')).toHaveValue(5);
expect(screen.getByLabelText('maxp')).toHaveValue(5);
expect(value).toBe(5);
await userEvent.clear(getByLabelText('maxp'));
await userEvent.type(getByLabelText('maxp'), '10');
waitFor(() => {
expect(onChange).toHaveBeenCalledWith(10);
});
await user.clear(screen.getByLabelText('maxp'));
expect(value).toBe(Number.POSITIVE_INFINITY);
await user.type(screen.getByLabelText('maxp'), '10');
expect(value).toBe(10);
});
});
@@ -23,7 +23,7 @@ import {
Typography,
} from '@material-ui/core';
import ClearIcon from '@material-ui/icons/Clear';
import React, { useCallback } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
export type Props = {
value: number;
@@ -42,18 +42,37 @@ const useStyles = makeStyles(
export const MaxDepthFilter = ({ value, onChange }: Props) => {
const classes = useStyles();
const onChangeRef = useRef(onChange);
const [currentValue, setCurrentValue] = useState(value);
// Keep a fresh reference to the latest callback
useEffect(() => {
onChangeRef.current = onChange;
}, [onChange]);
// If the value changes externally, update ourselves
useEffect(() => {
setCurrentValue(value);
}, [value]);
// When the entered text changes, update ourselves and communicate externally
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const v = Number(event.target.value);
onChange(v <= 0 ? Number.POSITIVE_INFINITY : v);
const newValueNumeric = Number(event.target.value);
const newValue =
Number.isFinite(newValueNumeric) && newValueNumeric > 0
? newValueNumeric
: Number.POSITIVE_INFINITY;
setCurrentValue(newValue);
onChangeRef.current(newValue);
},
[onChange],
[],
);
const reset = useCallback(() => {
onChange(Number.POSITIVE_INFINITY);
}, [onChange]);
setCurrentValue(Number.POSITIVE_INFINITY);
onChangeRef.current(Number.POSITIVE_INFINITY);
}, [onChangeRef]);
return (
<Box pb={1} pt={1}>
@@ -62,7 +81,7 @@ export const MaxDepthFilter = ({ value, onChange }: Props) => {
<OutlinedInput
type="number"
placeholder="∞ Infinite"
value={isFinite(value) ? value : ''}
value={Number.isFinite(currentValue) ? String(currentValue) : ''}
onChange={handleChange}
endAdornment={
<InputAdornment position="end">
@@ -75,7 +75,7 @@ describe('ComponentContextMenu', () => {
expect(button).toBeInTheDocument();
fireEvent.click(button);
const unregister = await screen.getByText('Unregister entity');
const unregister = screen.getByText('Unregister entity');
expect(unregister).toBeInTheDocument();
const unregisterSpanItem = getByText(/Unregister entity/);
@@ -69,7 +69,7 @@ describe('ComponentContextMenu', () => {
/>,
);
const unregister = await screen.getByText('Unregister entity');
const unregister = screen.getByText('Unregister entity');
expect(unregister).toBeInTheDocument();
const unregisterSpanItem = getByText(/Unregister entity/);
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { SyntheticsLocation } from './SyntheticsLocation';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
@@ -60,6 +61,6 @@ describe('SyntheticsLocation', () => {
</ApiProvider>,
);
expect(await rendered.findByText(/__location__/)).toBeInTheDocument();
expect(await rendered.queryByText(/failed/)).not.toBeInTheDocument();
expect(rendered.queryByText(/failed/)).not.toBeInTheDocument();
});
});
@@ -40,7 +40,7 @@ describe('Select', () => {
expect(rendered.getAllByText(testLabel)).toHaveLength(2);
});
describe('when the user hasn`t clicked on it', () => {
describe("when the user hasn't clicked on it", () => {
it('should only render the current select item', async () => {
const rendered = await renderInTestApp(
<SelectComponent
@@ -122,7 +122,7 @@ describe('<AuditListTableForEntity />', () => {
it('renders nothing', async () => {
const { queryByTestId } = subject();
expect(await queryByTestId('AuditListTable')).toBeNull();
expect(queryByTestId('AuditListTable')).toBeNull();
});
});
@@ -137,7 +137,7 @@ describe('<AuditListTableForEntity />', () => {
it('renders nothing', async () => {
const { queryByTestId } = subject();
expect(await queryByTestId('AuditListTable')).toBeNull();
expect(queryByTestId('AuditListTable')).toBeNull();
});
});
});
@@ -151,10 +151,10 @@ describe('<LastLighthouseAuditCard />', () => {
it('renders nothing', async () => {
const { queryByTestId } = subject();
expect(await queryByTestId('AuditListTable')).toBeNull();
expect(queryByTestId('AuditListTable')).toBeNull();
});
});
//
describe('where there is no data', () => {
beforeEach(() => {
(useWebsiteForEntity as jest.Mock).mockReturnValue({
@@ -166,7 +166,7 @@ describe('<LastLighthouseAuditCard />', () => {
it('renders nothing', async () => {
const { queryByTestId } = subject();
expect(await queryByTestId('AuditListTable')).toBeNull();
expect(queryByTestId('AuditListTable')).toBeNull();
});
});
});
@@ -15,7 +15,8 @@
*/
import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ApiProvider } from '@backstage/core-app-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { CatalogApi } from '@backstage/catalog-client';
@@ -74,13 +75,13 @@ describe('<GroupListPicker />', () => {
</ApiProvider>,
);
fireEvent.click(getByTestId('group-list-picker-button'));
await userEvent.click(getByTestId('group-list-picker-button'));
const input = getByTestId('group-list-picker-input').querySelector('input');
fireEvent.change(input as HTMLElement, { target: { value: 'GR' } });
await userEvent.type(input as HTMLElement, 'GR');
await waitFor(async () => {
expect(getByText('Group A')).toBeInTheDocument();
fireEvent.click(getByText('Group A'));
await userEvent.click(getByText('Group A'));
expect(getByText('Group A')).toBeInTheDocument();
});
});
+6 -6
View File
@@ -73,7 +73,7 @@ export class PagerDutyClient implements PagerDutyApi {
url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/pagerduty/services?${commonGetServiceParams}&query=${integrationKey}`;
const { services } = await this.getByUrl<PagerDutyServicesResponse>(url);
const { services } = await this.findByUrl<PagerDutyServicesResponse>(url);
const service = services[0];
if (!service) throw new NotFoundError();
@@ -84,7 +84,7 @@ export class PagerDutyClient implements PagerDutyApi {
'proxy',
)}/pagerduty/services/${serviceId}?${commonGetServiceParams}`;
response = await this.getByUrl<PagerDutyServiceResponse>(url);
response = await this.findByUrl<PagerDutyServiceResponse>(url);
} else {
throw new NotFoundError();
}
@@ -100,7 +100,7 @@ export class PagerDutyClient implements PagerDutyApi {
'proxy',
)}/pagerduty/incidents?${params}`;
return await this.getByUrl<PagerDutyIncidentsResponse>(url);
return await this.findByUrl<PagerDutyIncidentsResponse>(url);
}
async getChangeEventsByServiceId(
@@ -111,7 +111,7 @@ export class PagerDutyClient implements PagerDutyApi {
'proxy',
)}/pagerduty/services/${serviceId}/change_events?${params}`;
return await this.getByUrl<PagerDutyChangeEventsResponse>(url);
return await this.findByUrl<PagerDutyChangeEventsResponse>(url);
}
async getOnCallByPolicyId(
@@ -122,7 +122,7 @@ export class PagerDutyClient implements PagerDutyApi {
'proxy',
)}/pagerduty/oncalls?${params}`;
return await this.getByUrl<PagerDutyOnCallsResponse>(url);
return await this.findByUrl<PagerDutyOnCallsResponse>(url);
}
triggerAlarm(request: PagerDutyTriggerAlarmRequest): Promise<Response> {
@@ -158,7 +158,7 @@ export class PagerDutyClient implements PagerDutyApi {
return this.request(`${url}/enqueue`, options);
}
private async getByUrl<T>(url: string): Promise<T> {
private async findByUrl<T>(url: string): Promise<T> {
const options = {
method: 'GET',
headers: {
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Config } from '@backstage/config';
import { assertError, InputError } from '@backstage/errors';
import {
@@ -124,6 +125,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics(
topics: string[] | undefined,
logger: Logger,
) {
// eslint-disable-next-line testing-library/no-await-sync-query
const user = await client.rest.users.getByUsername({
username: owner,
});
@@ -236,7 +236,7 @@ describe('<ListTasksPage />', () => {
);
await act(async () => {
const allButton = await getByText('All');
const allButton = getByText('All');
fireEvent.click(allButton);
});
@@ -15,7 +15,6 @@
*/
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { OwnerListPicker } from './OwnerListPicker';
import { fireEvent } from '@testing-library/react';
@@ -29,8 +28,8 @@ describe('<OwnerListPicker />', () => {
const { getByText } = await renderInTestApp(<OwnerListPicker {...props} />);
expect(await getByText('Owned')).toBeDefined();
expect(await getByText('All')).toBeDefined();
expect(getByText('Owned')).toBeDefined();
expect(getByText('All')).toBeDefined();
});
it('should call the function on select other item', async () => {
@@ -41,7 +40,7 @@ describe('<OwnerListPicker />', () => {
const { getByText } = await renderInTestApp(<OwnerListPicker {...props} />);
fireEvent.click(await getByText('All'));
fireEvent.click(getByText('All'));
expect(props.onSelectOwner).toHaveBeenCalledWith('all');
});
});
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
MockAnalyticsApi,
renderInTestApp,
@@ -340,9 +341,9 @@ describe('TemplatePage', () => {
},
);
expect(await queryByText('Name')).not.toBeInTheDocument();
expect(await queryByText('Description')).toBeInTheDocument();
expect(await queryByText('Owner')).toBeInTheDocument();
expect(await queryByText('Send data')).toBeInTheDocument();
expect(queryByText('Name')).not.toBeInTheDocument();
expect(queryByText('Description')).toBeInTheDocument();
expect(queryByText('Owner')).toBeInTheDocument();
expect(queryByText('Send data')).toBeInTheDocument();
});
});
@@ -34,7 +34,7 @@ describe('RegisterExistingButton', () => {
<RegisterExistingButton title="Pick me" />,
);
expect(await queryByText('Pick me')).not.toBeInTheDocument();
expect(queryByText('Pick me')).not.toBeInTheDocument();
});
it('should not render if permissions are not allowed', async () => {
@@ -43,7 +43,7 @@ describe('RegisterExistingButton', () => {
<RegisterExistingButton title="Pick me" to="blah" />,
);
expect(await queryByText('Pick me')).not.toBeInTheDocument();
expect(queryByText('Pick me')).not.toBeInTheDocument();
});
it('should render the button with the text', async () => {
@@ -52,6 +52,6 @@ describe('RegisterExistingButton', () => {
<RegisterExistingButton title="Pick me" to="blah" />,
);
expect(await queryByText('Pick me')).toBeInTheDocument();
expect(queryByText('Pick me')).toBeInTheDocument();
});
});
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { ReviewState } from './ReviewState';
import { render } from '@testing-library/react';
@@ -97,9 +98,7 @@ describe('ReviewState', () => {
<ReviewState formState={formState} schemas={schemas} />,
);
expect(
await queryByRole('row', { name: 'Name ******' }),
).not.toBeInTheDocument();
expect(queryByRole('row', { name: 'Name ******' })).not.toBeInTheDocument();
});
it('should allow for masking an option with a set text', () => {
+7 -7
View File
@@ -64,7 +64,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
'proxy',
)}/splunk-on-call/v1/incidents`;
const { incidents } = await this.getByUrl<IncidentsResponse>(url);
const { incidents } = await this.findByUrl<IncidentsResponse>(url);
return incidents;
}
@@ -73,7 +73,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/oncall/current`;
const { teamsOnCall } = await this.getByUrl<OnCallsResponse>(url);
const { teamsOnCall } = await this.findByUrl<OnCallsResponse>(url);
return teamsOnCall;
}
@@ -82,7 +82,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/team`;
const teams = await this.getByUrl<Team[]>(url);
const teams = await this.findByUrl<Team[]>(url);
return teams;
}
@@ -91,7 +91,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/org/routing-keys`;
const { routingKeys } = await this.getByUrl<ListRoutingKeyResponse>(url);
const { routingKeys } = await this.findByUrl<ListRoutingKeyResponse>(url);
return routingKeys;
}
@@ -100,7 +100,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v2/user`;
const { users } = await this.getByUrl<ListUserResponse>(url);
const { users } = await this.findByUrl<ListUserResponse>(url);
return users;
}
@@ -109,7 +109,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
const url = `${await this.config.discoveryApi.getBaseUrl(
'proxy',
)}/splunk-on-call/v1/policies`;
const { policies } = await this.getByUrl<EscalationPolicyResponse>(url);
const { policies } = await this.findByUrl<EscalationPolicyResponse>(url);
return policies;
}
@@ -146,7 +146,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
return this.request(url, options);
}
private async getByUrl<T>(url: string): Promise<T> {
private async findByUrl<T>(url: string): Promise<T> {
const options = {
method: 'GET',
headers: {
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import {
@@ -187,7 +188,7 @@ describe('SplunkOnCallCard', () => {
{ timeout: 2000 },
);
const createIncidentButton = await getByText('Create Incident');
const createIncidentButton = getByText('Create Incident');
await act(async () => {
fireEvent.click(createIncidentButton);
});
+1 -1
View File
@@ -173,7 +173,7 @@ export const useShadowRootElements: <
) => TReturnedElement[];
// @public
export const useShadowRootSelection: (wait?: number) => Selection | null;
export const useShadowRootSelection: (waitMillis?: number) => Selection | null;
// @public
export const useTechDocsAddons: () => {
+4 -4
View File
@@ -57,10 +57,10 @@ const isValidSelection = (newSelection: Selection) => {
};
/**
* Hook for retreiving a selection within the ShadowRoot.
* Hook for retrieving a selection within the ShadowRoot.
* @public
*/
export const useShadowRootSelection = (wait: number = 0) => {
export const useShadowRootSelection = (waitMillis: number = 0) => {
const shadowRoot = useShadowRoot();
const [selection, setSelection] = useState<Selection | null>(null);
const handleSelectionChange = useMemo(
@@ -78,8 +78,8 @@ export const useShadowRootSelection = (wait: number = 0) => {
} else {
setSelection(null);
}
}, wait),
[shadowRoot, setSelection, wait],
}, waitMillis),
[shadowRoot, setSelection, waitMillis],
);
useEffect(() => {
@@ -103,10 +103,10 @@ describe('Entity List Docs Grid', () => {
},
);
expect(await screen.queryByText('All Documentation')).toBeInTheDocument();
expect(await screen.queryByText('Documentation #1')).toBeInTheDocument();
expect(await screen.queryByText('Documentation #2')).toBeInTheDocument();
expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument();
expect(screen.queryByText('All Documentation')).toBeInTheDocument();
expect(screen.queryByText('Documentation #1')).toBeInTheDocument();
expect(screen.queryByText('Documentation #2')).toBeInTheDocument();
expect(screen.queryByTestId('doc-not-found')).not.toBeInTheDocument();
});
it('should render only filtered entities with filtering', async () => {
@@ -131,14 +131,10 @@ describe('Entity List Docs Grid', () => {
},
);
expect(
await screen.queryByText('Curated Documentation'),
).toBeInTheDocument();
expect(await screen.queryByText('Documentation #1')).toBeInTheDocument();
expect(
await screen.queryByText('Documentation #2'),
).not.toBeInTheDocument();
expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument();
expect(screen.queryByText('Curated Documentation')).toBeInTheDocument();
expect(screen.queryByText('Documentation #1')).toBeInTheDocument();
expect(screen.queryByText('Documentation #2')).not.toBeInTheDocument();
expect(screen.queryByTestId('doc-not-found')).not.toBeInTheDocument();
});
it('should render nothing with filtering yielding no result', async () => {
@@ -163,16 +159,10 @@ describe('Entity List Docs Grid', () => {
},
);
expect(
await screen.queryByText('Curated Documentation'),
).not.toBeInTheDocument();
expect(
await screen.queryByText('Documentation #1'),
).not.toBeInTheDocument();
expect(
await screen.queryByText('Documentation #2'),
).not.toBeInTheDocument();
expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument();
expect(screen.queryByText('Curated Documentation')).not.toBeInTheDocument();
expect(screen.queryByText('Documentation #1')).not.toBeInTheDocument();
expect(screen.queryByText('Documentation #2')).not.toBeInTheDocument();
expect(screen.queryByTestId('doc-not-found')).not.toBeInTheDocument();
});
it('should render an error without any documentation and without filtering', async () => {
@@ -189,15 +179,9 @@ describe('Entity List Docs Grid', () => {
},
);
expect(
await screen.queryByText('All Documentation'),
).not.toBeInTheDocument();
expect(
await screen.queryByText('Documentation #1'),
).not.toBeInTheDocument();
expect(
await screen.queryByText('Documentation #2'),
).not.toBeInTheDocument();
expect(await screen.queryByTestId('doc-not-found')).toBeInTheDocument();
expect(screen.queryByText('All Documentation')).not.toBeInTheDocument();
expect(screen.queryByText('Documentation #1')).not.toBeInTheDocument();
expect(screen.queryByText('Documentation #2')).not.toBeInTheDocument();
expect(screen.queryByTestId('doc-not-found')).toBeInTheDocument();
});
});