Skip to content

Commit

Permalink
Chore/eslint for testing library (#1020)
Browse files Browse the repository at this point in the history
* Added ESLint to ensure we follow the best practices due to the testing-libraru. For now, I have disabled some lines due to much work to fix them within this PR.
  • Loading branch information
framitdavid authored Mar 21, 2023
1 parent 03067a9 commit 6330a6c
Show file tree
Hide file tree
Showing 38 changed files with 252 additions and 116 deletions.
13 changes: 11 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"preferred-import-path",
"simple-import-sort",
"cypress",
"unused-imports"
"unused-imports",
"testing-library"
],
"settings": {
"import/extensions": [".js", ".jsx", ".ts", ".tsx"],
Expand All @@ -41,7 +42,8 @@
},
"react": {
"version": "detect"
}
},
"testing-library/custom-queries": "off"
},
"ignorePatterns": [
"node_modules",
Expand Down Expand Up @@ -182,6 +184,13 @@
"rules": {
"react-hooks/rules-of-hooks": "off"
}
},
{
"files": ["*.test.ts", "*.test.tsx"],
"extends": ["plugin:testing-library/react"],
"rules": {
"testing-library/no-unnecessary-act": "error"
}
}
]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-simple-import-sort": "10.0.0",
"eslint-plugin-sonarjs": "0.18.0",
"eslint-plugin-testing-library": "^5.10.2",
"eslint-plugin-unused-imports": "^2.0.0",
"execa": "7.1.0",
"fork-ts-checker-notifier-webpack-plugin": "6.0.0",
Expand Down
12 changes: 8 additions & 4 deletions src/components/AltinnInput.test.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import React from 'react';

import { act, render as rtlRender, screen } from '@testing-library/react';
import { act, render as rtlRender, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { AltinnInput } from 'src/components/AltinnInput';
import type { IAltinnInputProps } from 'src/components/AltinnInput';

const render = (props: Partial<IAltinnInputProps> = {}) => {
const user = userEvent.setup();
const allProps = {
label: 'inputLabel',
...props,
};

rtlRender(<AltinnInput {...allProps} />);
return { user };
};

describe('AltinnInput', () => {
it('should call onChange when typing in the input field', async () => {
const handleChange = jest.fn();
render({ onChange: handleChange });
const { user } = render({ onChange: handleChange });

const input = screen.getByRole('textbox', {
name: /inputlabel/i,
});

await act(() => userEvent.type(input, 'input-text'));
await act(() => user.type(input, 'input-text'));

await waitFor(() => expect(input).toHaveFocus());
await waitFor(() => expect(input).toHaveValue('input-text'));
expect(handleChange).toHaveBeenCalled();
});

Expand All @@ -38,7 +42,7 @@ describe('AltinnInput', () => {
it('should add input-validation-error id when applying inputValidationError class', () => {
render({ validationError: true });
expect(screen.getByTestId(/input-validation-error/i)).toBeInTheDocument();
expect(screen.queryByTestId(/input-validation-error/i)).not.toBeNull();
expect(screen.getByTestId(/input-validation-error/i)).not.toBeNull();
});

it('should not add input-validation-error id when applying inputValidationError class', () => {
Expand Down
15 changes: 8 additions & 7 deletions src/components/AltinnInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ export interface IAltinnInputProps extends React.InputHTMLAttributes<any> {

const useStyles = makeStyles(styles);

export function AltinnInput(props: IAltinnInputProps) {
export function AltinnInput({
showLabel = true,
validationError = false,
label,
widthPercentage,
iconString,
...rest
}: IAltinnInputProps) {
const inputRef = React.createRef<HTMLInputElement>();
const { iconString, label, widthPercentage, showLabel, validationError, ...rest } = props;
const classes = useStyles();

function focusInput() {
Expand Down Expand Up @@ -96,8 +102,3 @@ export function AltinnInput(props: IAltinnInputProps) {
</Grid>
);
}

AltinnInput.defaultProps = {
showLabel: true,
validationError: false,
};
3 changes: 3 additions & 0 deletions src/components/AltinnLoader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ describe('AltinnLoader', () => {
render({});
expect(screen.getByText('hidden text')).toBeInTheDocument();
expect(screen.getByText('hidden text')).toHaveClass('sr-only');
// eslint-disable-next-line testing-library/no-node-access
expect(screen.getByText('hidden text').parentElement).toHaveAttribute('id', 'altinn-loader');
});
it('should have the id assigned', () => {
render({
id: 'some-id',
});
// eslint-disable-next-line testing-library/no-node-access
expect(screen.getByText('hidden text').parentElement).toHaveAttribute('id', 'some-id');
});

it('should append className', () => {
render({
className: 'some-class',
});
// eslint-disable-next-line testing-library/no-node-access
expect(screen.getByText('hidden text').parentElement).toHaveClass('some-class');
});

Expand Down
2 changes: 2 additions & 0 deletions src/components/LandmarkShortcuts.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('LandmarkShortcuts.tsx', () => {
it('should set active element to main when nav is clicked', async () => {
renderMainContentNav();
await userEvent.click(screen.getByRole('link'));
// eslint-disable-next-line testing-library/no-node-access
expect(document.activeElement).toBe(document.getElementById('main-content'));
});

Expand All @@ -23,6 +24,7 @@ describe('LandmarkShortcuts.tsx', () => {
shortcuts: [{ text: 'Hopp til hovedinnhold', id: 'other-content' }],
});
await userEvent.click(screen.getByRole('link'));
// eslint-disable-next-line testing-library/no-node-access
expect(document.getElementById('other-content')?.tabIndex).toBe(-2);
});

Expand Down
11 changes: 11 additions & 0 deletions src/components/hooks/useDelayedSavedState.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { act, renderHook, waitFor } from '@testing-library/react';

import { useDelayedSavedState } from 'src/components/hooks/useDelayedSavedState';

test('should debounce input by default', async () => {
const handleDataChangeMock = jest.fn();
const { result } = renderHook(() => useDelayedSavedState(handleDataChangeMock));

act(() => result.current.setValue('great text'));
await waitFor(() => expect(result.current.value).toBe('great text'));
});
6 changes: 3 additions & 3 deletions src/components/message/ErrorPaper.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';

import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';

import { ErrorPaper } from 'src/components/message/ErrorPaper';

describe('ErrorPaper', () => {
it('should render the supplied message', async () => {
const utils = render(<ErrorPaper message='mock message' />);
const item = await utils.findByText('mock message');
render(<ErrorPaper message='mock message' />);
const item = await screen.findByText('mock message');
expect(item).not.toBe(null);
});
});
3 changes: 3 additions & 0 deletions src/components/message/ErrorReport.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe('ErrorReport', () => {
// Unmapped errors should not be clickable
const errorNode = screen.getByText('some unmapped error');
expect(errorNode).toBeInTheDocument();
// eslint-disable-next-line testing-library/no-node-access
expect(errorNode.parentElement?.tagName).toEqual('LI');
});

Expand All @@ -70,7 +71,9 @@ describe('ErrorReport', () => {

const errorNode = screen.getByText('some mapped error');
expect(errorNode).toBeInTheDocument();
// eslint-disable-next-line testing-library/no-node-access
expect(errorNode.parentElement?.parentElement?.tagName).toEqual('LI');
// eslint-disable-next-line testing-library/no-node-access
expect(errorNode.parentElement?.tagName).toEqual('BUTTON');
});
});
4 changes: 4 additions & 0 deletions src/components/organisms/AltinnAppHeader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('organisms/AltinnAppHeader', () => {
const profileButton = screen.getByRole('button', {
name: /profilikon meny/i,
});
// eslint-disable-next-line testing-library/no-node-access
expect(profileButton.firstChild?.firstChild).toHaveClass('fa-private-circle-big');
});

Expand All @@ -62,6 +63,7 @@ describe('organisms/AltinnAppHeader', () => {
const profileButton = screen.getByRole('button', {
name: /profilikon meny/i,
});
// eslint-disable-next-line testing-library/no-node-access
expect(profileButton.firstChild?.firstChild).toHaveClass('fa-private-circle-big');
});

Expand All @@ -70,6 +72,7 @@ describe('organisms/AltinnAppHeader', () => {
const profileButton = screen.getByRole('button', {
name: /profilikon meny/i,
});
// eslint-disable-next-line testing-library/no-node-access
expect(profileButton.firstChild?.firstChild).toHaveClass('fa-corp-circle-big');
});

Expand All @@ -81,6 +84,7 @@ describe('organisms/AltinnAppHeader', () => {
hidden: true,
}),
).toBeNull();
// eslint-disable-next-line testing-library/no-unnecessary-act
await act(() =>
userEvent.click(
screen.getByRole('button', {
Expand Down
5 changes: 5 additions & 0 deletions src/components/summary/SummaryComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ describe('SummaryComponent', () => {
};
otherLayout.uiConfig.hiddenFields = ['Input'];
const { container } = renderHelper({ componentRef: 'Input' }, {}, otherLayout);
// eslint-disable-next-line testing-library/no-node-access
expect(container.firstChild).toBeNull();
// eslint-disable-next-line testing-library/no-node-access
expect(container.childElementCount).toBe(0);
});

Expand All @@ -103,7 +105,10 @@ describe('SummaryComponent', () => {
...layoutMock(),
};
otherLayout.uiConfig.currentView = 'otherPage';
// eslint-disable-next-line testing-library/render-result-naming-convention
const theRender = renderHelper({ componentRef: 'Input' }, {}, otherLayout);

// eslint-disable-next-line
const button = theRender.container.querySelector<HTMLButtonElement>('button');
button && fireEvent.click(button);
expect(spy).toHaveBeenCalledWith({
Expand Down
5 changes: 3 additions & 2 deletions src/features/confirm/containers/ConfirmPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('ConfirmPage', () => {
});

it('should show loading when clicking submit', async () => {
const user = userEvent.setup();
const { store } = renderWithProviders(
<MemoryRouter>
<ConfirmPage {...props} />
Expand All @@ -79,9 +80,9 @@ describe('ConfirmPage', () => {
expect(dispatch).toHaveBeenCalledTimes(0);
expect(screen.queryByText(loadingText)).not.toBeInTheDocument();
expect(submitBtn).toBeInTheDocument();
await act(() => userEvent.click(submitBtn));
await act(() => user.click(submitBtn));

expect(screen.queryByText(submitBtnText)).toBeInTheDocument();
expect(screen.getByText(submitBtnText)).toBeInTheDocument();
expect(screen.getByText(loadingText)).toBeInTheDocument();
expect(dispatch).toHaveBeenCalledTimes(0);
});
Expand Down
26 changes: 13 additions & 13 deletions src/features/form/components/Label.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';

import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import configureStore from 'redux-mock-store';

import { Label } from 'src/features/form/components/Label';
Expand All @@ -12,44 +12,44 @@ describe('Label', () => {
const optionalMarking = 'Valgfri';

it('should render label', () => {
const { queryByText } = renderLabelComponent();
expect(queryByText('label.text')).toBeInTheDocument();
renderLabelComponent();
expect(screen.getByText('label.text')).toBeInTheDocument();
});

it('should render required marking when field is required', () => {
const { queryByText } = renderLabelComponent({ required: true });
expect(queryByText(requiredMarking)).toBeTruthy();
renderLabelComponent({ required: true });
expect(screen.getByText(requiredMarking)).toBeTruthy();
});

it('should not render required marking when field is readOnly', () => {
const { queryByText } = renderLabelComponent({
renderLabelComponent({
required: true,
readOnly: true,
});
expect(queryByText(requiredMarking)).toBeFalsy();
expect(screen.queryByText(requiredMarking)).toBeFalsy();
});

it('should render optional marking when labelSettings.optionalIndicator is true, and required, readOnly are both false', () => {
const { queryByText } = renderLabelComponent({
renderLabelComponent({
labelSettings: { optionalIndicator: true },
});
expect(queryByText(`(${optionalMarking})`)).toBeTruthy();
expect(screen.getByText(`(${optionalMarking})`)).toBeTruthy();
});

it('should not render optional marking when required, even if labelSettings.optionalIndicator is true', () => {
const { queryByText } = renderLabelComponent({
renderLabelComponent({
labelSettings: { optionalIndicator: true },
required: true,
});
expect(queryByText(` (${optionalMarking})`)).toBeFalsy();
expect(screen.queryByText(` (${optionalMarking})`)).toBeFalsy();
});

it('should not render optional marking when readOnly, even if labelSettings.optionalIndicator is true', () => {
const { queryByText } = renderLabelComponent({
renderLabelComponent({
labelSettings: { optionalIndicator: true },
readOnly: true,
});
expect(queryByText(` (${optionalMarking})`)).toBeFalsy();
expect(screen.queryByText(` (${optionalMarking})`)).toBeFalsy();
});

function renderLabelComponent(props: Partial<IFormLabelProps> = {}) {
Expand Down
18 changes: 9 additions & 9 deletions src/features/form/components/Legend.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Provider } from 'react-redux';

import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import configureStore from 'redux-mock-store';

import { Legend } from 'src/features/form/components/Legend';
Expand Down Expand Up @@ -44,27 +44,27 @@ describe('Legend', () => {
}

it('should render legend', () => {
const { queryByText } = renderLegendComponent();
expect(queryByText('label.text')).toBeInTheDocument();
renderLegendComponent();
expect(screen.getByText('label.text')).toBeInTheDocument();
});

it('should render required marking when field is required', () => {
const { queryByText } = renderLegendComponent({ required: true });
expect(queryByText(requiredMarking)).toBeTruthy();
renderLegendComponent({ required: true });
expect(screen.getByText(requiredMarking)).toBeTruthy();
});

it('should render optional marking when labelSettings.optionalIndicator is true', () => {
const { queryByText } = renderLegendComponent({
renderLegendComponent({
labelSettings: { optionalIndicator: true },
});
expect(queryByText(`(${optionalMarking})`)).toBeTruthy();
expect(screen.getByText(`(${optionalMarking})`)).toBeTruthy();
});

it('should not render optional marking when required, even if labelSettings.optionalIndicator is true', () => {
const { queryByText } = renderLegendComponent({
renderLegendComponent({
labelSettings: { optionalIndicator: true },
required: true,
});
expect(queryByText(`(${optionalMarking})`)).toBeFalsy();
expect(screen.queryByText(`(${optionalMarking})`)).toBeFalsy();
});
});
Loading

0 comments on commit 6330a6c

Please sign in to comment.