Updated component tests to use best practices and improved syntax

This commit is contained in:
sam-keen 2022-03-18 14:41:30 +00:00
parent 504f088f00
commit edbf8244df
7 changed files with 77 additions and 89 deletions

View File

@ -4,7 +4,7 @@ interface SecondsAgoProps {
date: string | undefined; date: string | undefined;
} }
export const SecondsAgo = ({ date }: SecondsAgoProps) => { export const SecondsAgo = ({ date, ...props }: SecondsAgoProps) => {
const [now, setNow] = useState(Date.now()); const [now, setNow] = useState(Date.now());
useEffect(() => { useEffect(() => {
@ -18,10 +18,16 @@ export const SecondsAgo = ({ date }: SecondsAgoProps) => {
return <>Date unknown</>; return <>Date unknown</>;
} }
console.log(
`now: ${now}, before: ${new Date(
date
).getTime()}, date getting passed in: ${date}`
);
const timeAgoInSeconds = Math.floor((now - new Date(date).getTime()) / 1000); const timeAgoInSeconds = Math.floor((now - new Date(date).getTime()) / 1000);
return ( return (
<div> <div {...props}>
{timeAgoInSeconds === 1 ? '1 second' : `${timeAgoInSeconds} seconds`} ago {timeAgoInSeconds === 1 ? '1 second' : `${timeAgoInSeconds} seconds`} ago
</div> </div>
); );

View File

@ -1,24 +1,37 @@
import { render, screen } from '@testing-library/react'; import { render, screen, act } from '@testing-library/react';
import { SecondsAgo } from './index'; import { SecondsAgo } from './index';
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
describe('Seconds ago', () => { describe('Seconds ago', () => {
it('should render successfully', () => { it('should render successfully', () => {
const dateInString = Date.now().toString(); const dateInString = new Date().toString();
const { baseElement } = render(<SecondsAgo date={dateInString} />); render(<SecondsAgo data-testid="test-seconds-ago" date={dateInString} />);
expect(baseElement).toBeTruthy();
expect(screen.getByTestId('test-seconds-ago')).toBeInTheDocument();
}); });
it('should show the correct amount of seconds ago', async () => { it('should show the correct amount of seconds ago', (done) => {
const secondsToWait = 2; const secondsToWait = 10;
const dateInString = new Date().toString(); const dateInString = new Date().toString();
await new Promise((r) => setTimeout(r, secondsToWait * 1000)); act(() => {
jest.advanceTimersByTime(secondsToWait * 1000);
});
render(<SecondsAgo date={dateInString} />); jest.runOnlyPendingTimers();
done();
expect( render(<SecondsAgo data-testid="test-seconds-ago" date={dateInString} />);
screen.getByText(`${secondsToWait} seconds ago`)
).toBeInTheDocument(); expect(screen.getByTestId('test-seconds-ago')).toHaveTextContent(
`${secondsToWait} seconds ago`
);
}); });
}); });

View File

@ -6,7 +6,15 @@ interface StatusMessageProps {
className?: string; className?: string;
} }
export const StatusMessage = ({ children, className }: StatusMessageProps) => { export const StatusMessage = ({
children,
className,
...props
}: StatusMessageProps) => {
const classes = classnames('font-alpha text-h4 mb-28', className); const classes = classnames('font-alpha text-h4 mb-28', className);
return <h3 className={classes}>{children}</h3>; return (
<h3 className={classes} {...props}>
{children}
</h3>
);
}; };

View File

@ -1,10 +1,11 @@
import { render } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { StatusMessage } from './index'; import { StatusMessage } from './index';
describe('Status message', () => { describe('Status message', () => {
it('should render successfully', () => { it('should render successfully', () => {
const { baseElement } = render(<StatusMessage>test</StatusMessage>); render(
expect(baseElement).toBeTruthy(); <StatusMessage data-testid="status-message-test">test</StatusMessage>
);
expect(screen.getByTestId('status-message-test')).toBeInTheDocument();
}); });
}); });

View File

@ -1,9 +1,9 @@
import { render } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { Table, TableRow, TableHeader, TableCell } from './index'; import { Table, TableRow, TableHeader, TableCell } from './index';
describe('Renders all table components', () => { describe('Renders all table components', () => {
const { container } = render( render(
<Table data-testid="test-table"> <Table data-testid="test-table">
<TableRow data-testid="test-tr"> <TableRow data-testid="test-tr">
<TableHeader data-testid="test-th">Title</TableHeader> <TableHeader data-testid="test-th">Title</TableHeader>
@ -12,110 +12,68 @@ describe('Renders all table components', () => {
</Table> </Table>
); );
const table = container.querySelector('[data-testid="test-table"]'); expect(screen.getByTestId('test-table')).toBeInTheDocument();
const tr = container.querySelector('[data-testid="test-tr"]'); expect(screen.getByTestId('test-tr')).toBeInTheDocument();
const th = container.querySelector('[data-testid="test-th"]'); expect(screen.getByTestId('test-th')).toHaveTextContent('Title');
const td = container.querySelector('[data-testid="test-td"]'); expect(screen.getByTestId('test-td')).toHaveTextContent('Content');
expect(table).toBeInTheDocument();
expect(th).toBeInTheDocument();
expect(tr).toBeInTheDocument();
expect(td).toBeInTheDocument();
}); });
describe('Table row', () => { describe('Table row', () => {
it('should include classes based on custom "modifier" prop', () => { it('should include classes based on custom "modifier" prop', () => {
const { baseElement: withoutModifier } = render( render(
<Table> <Table>
<TableRow> <TableRow data-testid="modifier-test" modifier="bordered">
<TableCell>Without modifier</TableCell>
</TableRow>
</Table>
);
const { baseElement: withModifier } = render(
<Table>
<TableRow modifier="bordered">
<TableCell>With modifier</TableCell> <TableCell>With modifier</TableCell>
</TableRow> </TableRow>
</Table> </Table>
); );
const noModifierTr = withoutModifier.querySelector('tr'); expect(screen.getByTestId('modifier-test')).toHaveClass('border-white-40');
const modifierTr = withModifier.querySelector('tr');
const classNameToCheck = 'border-white-40';
expect(noModifierTr && !noModifierTr.classList.contains(classNameToCheck));
expect(modifierTr && modifierTr.classList.contains(classNameToCheck));
}); });
}); });
describe('Table header', () => { describe('Table header', () => {
it('should accept props i.e. scope="row"', () => { it('should accept props i.e. scope="row"', () => {
const { baseElement } = render( render(
<Table> <Table>
<TableRow> <TableRow>
<TableHeader scope="row">Test</TableHeader> <TableHeader data-testid="props-test" scope="row">
Test
</TableHeader>
</TableRow> </TableRow>
</Table> </Table>
); );
const th = baseElement.querySelector('th'); expect(screen.getByTestId('props-test')).toHaveAttribute('scope');
expect(th && th.hasAttribute('scope'));
}); });
it('should include custom class based on scope="row"', () => { it('should include custom class based on scope="row"', () => {
const { baseElement: withoutScope } = render( render(
<Table> <Table>
<TableRow> <TableRow>
<TableHeader>Without scope attribute</TableHeader> <TableHeader data-testid="scope-class-test" scope="row">
With scope attribute
</TableHeader>
</TableRow> </TableRow>
</Table> </Table>
); );
const { baseElement: withScope } = render( expect(screen.getByTestId('scope-class-test')).toHaveClass('text-left');
<Table>
<TableRow>
<TableHeader scope="row">With scope attribute</TableHeader>
</TableRow>
</Table>
);
const withoutScopeTr = withoutScope.querySelector('tr');
const withScopeTr = withScope.querySelector('tr');
const classNameToCheck = 'text-left';
expect(
withoutScopeTr && !withoutScopeTr.classList.contains(classNameToCheck)
);
expect(withScopeTr && withScopeTr.classList.contains(classNameToCheck));
}); });
}); });
describe('Table cell', () => { describe('Table cell', () => {
it('should include class based on custom "modifier" prop', () => { it('should include class based on custom "modifier" prop', () => {
const { baseElement: withoutModifier } = render( render(
<Table> <Table>
<TableRow> <TableRow>
<TableCell>Without modifier</TableCell> <TableCell data-testid="modifier-class-test" modifier="bordered">
With modifier
</TableCell>
</TableRow> </TableRow>
</Table> </Table>
); );
const { baseElement: withModifier } = render( expect(screen.getByTestId('modifier-class-test')).toHaveClass('py-4');
<Table>
<TableRow>
<TableCell modifier="bordered">With modifier</TableCell>
</TableRow>
</Table>
);
const noModifierTd = withoutModifier.querySelector('td');
const modifierTd = withModifier.querySelector('td');
const classNameToCheck = 'py-4';
expect(noModifierTd && !noModifierTd.classList.contains(classNameToCheck));
expect(modifierTd && modifierTd.classList.contains(classNameToCheck));
}); });
}); });

View File

@ -3,11 +3,11 @@ import { TruncateInline } from './truncate';
describe('Truncate', () => { describe('Truncate', () => {
it('should render successfully', () => { it('should render successfully', () => {
const { baseElement } = render( render(
<TruncateInline text={'Texty McTextFace'} /> <TruncateInline data-testid="truncate-test" text={'Texty McTextFace'} />
); );
expect(baseElement).toBeTruthy(); expect(screen.getByTestId('truncate-test')).toBeInTheDocument();
}); });
it('it truncates as expected', () => { it('it truncates as expected', () => {

View File

@ -22,6 +22,7 @@ export function TruncateInline({
children, children,
startChars, startChars,
endChars, endChars,
...props
}: TruncateInlineProps) { }: TruncateInlineProps) {
if (text === null) { if (text === null) {
return <span data-testid="empty-truncation" />; return <span data-testid="empty-truncation" />;
@ -31,6 +32,7 @@ export function TruncateInline({
const wrapperProps = { const wrapperProps = {
title: text, title: text,
className, className,
...props,
}; };
if (children !== undefined) { if (children !== undefined) {