Convert old tests to use "react-testing-library"

This commit is contained in:
Justin 2022-04-04 11:53:47 -04:00
parent 9a099b3fa7
commit ed47cf5f09
71 changed files with 411 additions and 1515 deletions

View file

@ -217,15 +217,15 @@ module.exports = {
'import/newline-after-import': 'error',
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'webpack/**',
'app/soapbox/test_setup.js',
'app/soapbox/test_helpers.js',
'app/**/__tests__/**',
'app/**/__mocks__/**',
],
},
// {
// devDependencies: [
// 'webpack/**',
// 'app/soapbox/test_setup.js',
// 'app/soapbox/test_helpers.js',
// 'app/**/__tests__/**',
// 'app/**/__mocks__/**',
// ],
// },
],
'import/no-unresolved': 'error',
'import/no-webpack-loader-syntax': 'error',

View file

@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import { Map as ImmutableMap } from 'immutable';
import { staticClient } from 'soapbox/api';
import { mockStore } from 'soapbox/test_helpers';
import { mockStore } from 'soapbox/jest/test-helpers';
import {
FETCH_ABOUT_PAGE_REQUEST,

View file

@ -1,6 +1,6 @@
import { mockStore } from 'soapbox/jest/test-helpers';
import { InstanceRecord } from 'soapbox/normalizers';
import rootReducer from 'soapbox/reducers';
import { mockStore } from 'soapbox/test_helpers';
import { uploadCompose } from '../compose';

View file

@ -1,7 +1,7 @@
import { Map as ImmutableMap } from 'immutable';
import { __stub } from 'soapbox/api';
import { mockStore } from 'soapbox/test_helpers';
import { mockStore } from 'soapbox/jest/test-helpers';
import { VERIFY_CREDENTIALS_REQUEST } from '../auth';
import { ACCOUNTS_IMPORT } from '../importer';

View file

@ -1,6 +1,6 @@
import { STATUSES_IMPORT } from 'soapbox/actions/importer';
import { __stub } from 'soapbox/api';
import { mockStore, rootState } from 'soapbox/test_helpers';
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
import { fetchContext } from '../statuses';

View file

@ -1,27 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AutosuggestEmoji /> renders emoji with custom url 1`] = `
<div
className="autosuggest-emoji"
>
<img
alt="foobar"
className="emojione"
src="http://example.com/emoji.png"
/>
:foobar:
</div>
`;
exports[`<AutosuggestEmoji /> renders native emoji 1`] = `
<div
className="autosuggest-emoji"
>
<img
alt="💙"
className="emojione"
src="/packs/emoji/1f499.svg"
/>
:foobar:
</div>
`;

View file

@ -1,37 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Avatar /> Autoplay renders an animated avatar 1`] = `
<div
className="rounded-full still-image"
style={
Object {
"height": "100px",
"width": "100px",
}
}
>
<img
alt=""
onLoad={[Function]}
src="/animated/alice.gif"
/>
</div>
`;
exports[`<Avatar /> Still renders a still avatar 1`] = `
<div
className="rounded-full still-image"
style={
Object {
"height": "100px",
"width": "100px",
}
}
>
<img
alt=""
onLoad={[Function]}
src="/animated/alice.gif"
/>
</div>
`;

View file

@ -1,28 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
<div
className="account__avatar-overlay"
>
<div
className="account__avatar-overlay-base still-image"
style={Object {}}
>
<img
alt=""
onLoad={[Function]}
src="/animated/alice.gif"
/>
</div>
<div
className="account__avatar-overlay-overlay still-image"
style={Object {}}
>
<img
alt=""
onLoad={[Function]}
src="/animated/eve.gif"
/>
</div>
</div>
`;

View file

@ -1,9 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Badge /> renders correctly 1`] = `
<span
className="badge badge--patron"
>
Patron
</span>
`;

View file

@ -1,16 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ColumnBackButton /> renders correctly 1`] = `
<button
className="column-back-button"
onClick={[Function]}
onKeyUp={[Function]}
>
<i
alt="chevron-left"
className="fa fa-chevron-left column-back-button__icon fa-fw"
role="img"
/>
Back
</button>
`;

View file

@ -1,35 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<DisplayName /> renders display name + account name 1`] = `
<span
className="display-name"
>
<span
className="hover-ref-wrapper"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<span
className="display-name__name"
>
<bdi>
<strong
className="display-name__html"
dangerouslySetInnerHTML={
Object {
"__html": "bar",
}
}
/>
</bdi>
</span>
</span>
<span
className="display-name__account"
>
@
bar@baz
</span>
</span>
`;

View file

@ -1,86 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<EmojiSelector /> renders correctly 1`] = `
<div
onBlur={[Function]}
onFocus={[Function]}
tabIndex="-1"
>
<div
className="flex space-x-2 bg-white dark:bg-slate-900 p-3 rounded-full shadow-md z-[999] w-max"
>
<button
className=""
onClick={[Function]}
tabIndex={-1}
>
<img
alt="👍"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f44d.svg"
/>
</button>
<button
className=""
onClick={[Function]}
tabIndex={-1}
>
<img
alt="❤"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/2764.svg"
/>
</button>
<button
className=""
onClick={[Function]}
tabIndex={-1}
>
<img
alt="😆"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f606.svg"
/>
</button>
<button
className=""
onClick={[Function]}
tabIndex={-1}
>
<img
alt="😮"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f62e.svg"
/>
</button>
<button
className=""
onClick={[Function]}
tabIndex={-1}
>
<img
alt="😢"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f622.svg"
/>
</button>
<button
className=""
onClick={[Function]}
tabIndex={-1}
>
<img
alt="😩"
className="w-8 h-8 duration-100 hover:scale-125"
draggable="false"
src="/packs/emoji/1f629.svg"
/>
</button>
</div>
</div>
`;

View file

@ -1,80 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<TimelineQueueButtonHeader /> renders correctly 1`] = `
<div
className="left-1/2 -translate-x-1/2 fixed top-20 z-50 hidden"
>
<a
className="flex items-center bg-primary-600 hover:bg-primary-700 hover:scale-105 active:scale-100 transition-transform text-white rounded-full px-4 py-2 space-x-1.5 cursor-pointer"
onClick={[Function]}
>
<div
className="svg-icon"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
</a>
</div>
`;
exports[`<TimelineQueueButtonHeader /> renders correctly 2`] = `
<div
className="left-1/2 -translate-x-1/2 fixed top-20 z-50 hidden"
>
<a
className="flex items-center bg-primary-600 hover:bg-primary-700 hover:scale-105 active:scale-100 transition-transform text-white rounded-full px-4 py-2 space-x-1.5 cursor-pointer"
onClick={[Function]}
>
<div
className="svg-icon"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
<p
className="text-sm text-inherit font-normal tracking-normal font-sans"
>
Click to see 1 new post
</p>
</a>
</div>
`;
exports[`<TimelineQueueButtonHeader /> renders correctly 3`] = `
<div
className="left-1/2 -translate-x-1/2 fixed top-20 z-50 hidden"
>
<a
className="flex items-center bg-primary-600 hover:bg-primary-700 hover:scale-105 active:scale-100 transition-transform text-white rounded-full px-4 py-2 space-x-1.5 cursor-pointer"
onClick={[Function]}
>
<div
className="svg-icon"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
<p
className="text-sm text-inherit font-normal tracking-normal font-sans"
>
Click to see 9999999 new posts
</p>
</a>
</div>
`;

View file

@ -1,6 +1,6 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '../../jest/test-helpers';
import AutosuggestEmoji from '../autosuggest_emoji';
describe('<AutosuggestEmoji />', () => {
@ -9,10 +9,11 @@ describe('<AutosuggestEmoji />', () => {
native: '💙',
colons: ':foobar:',
};
const component = renderer.create(<AutosuggestEmoji emoji={emoji} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
render(<AutosuggestEmoji emoji={emoji} />);
expect(screen.getByTestId('emoji')).toHaveTextContent('foobar');
expect(screen.getByRole('img').getAttribute('src')).not.toBe('http://example.com/emoji.png');
});
it('renders emoji with custom url', () => {
@ -22,9 +23,10 @@ describe('<AutosuggestEmoji />', () => {
native: 'foobar',
colons: ':foobar:',
};
const component = renderer.create(<AutosuggestEmoji emoji={emoji} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
render(<AutosuggestEmoji emoji={emoji} />);
expect(screen.getByTestId('emoji')).toHaveTextContent('foobar');
expect(screen.getByRole('img').getAttribute('src')).toBe('http://example.com/emoji.png');
});
});

View file

@ -1,38 +0,0 @@
import { fromJS } from 'immutable';
import React from 'react';
import { createComponent } from 'soapbox/test_helpers';
import Avatar from '../avatar';
describe('<Avatar />', () => {
const account = fromJS({
username: 'alice',
acct: 'alice',
display_name: 'Alice',
avatar: '/animated/alice.gif',
avatar_static: '/static/alice.jpg',
});
const size = 100;
describe('Autoplay', () => {
it('renders an animated avatar', () => {
const component = createComponent(<Avatar account={account} animate size={size} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('Still', () => {
it('renders a still avatar', () => {
const component = createComponent(<Avatar account={account} size={size} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
// TODO add autoplay test if possible
});

View file

@ -0,0 +1,35 @@
import { fromJS } from 'immutable';
import React from 'react';
import { render, screen } from '../../jest/test-helpers';
import Avatar from '../avatar';
describe('<Avatar />', () => {
const account = fromJS({
username: 'alice',
acct: 'alice',
display_name: 'Alice',
avatar: '/animated/alice.gif',
avatar_static: '/static/alice.jpg',
});
const size = 100;
// describe('Autoplay', () => {
// it('renders an animated avatar', () => {
// render(<Avatar account={account} animate size={size} />);
// expect(screen.getByRole('img').getAttribute('src')).toBe(account.get('avatar'));
// });
// });
describe('Still', () => {
it('renders a still avatar', () => {
render(<Avatar account={account} size={size} />);
expect(screen.getByRole('img').getAttribute('src')).toBe(account.get('avatar'));
});
});
// TODO add autoplay test if possible
});

View file

@ -1,8 +1,7 @@
import { fromJS } from 'immutable';
import React from 'react';
import { createComponent } from 'soapbox/test_helpers';
import { render, screen } from '../../jest/test-helpers';
import AvatarOverlay from '../avatar_overlay';
describe('<AvatarOverlay', () => {
@ -23,9 +22,7 @@ describe('<AvatarOverlay', () => {
});
it('renders a overlay avatar', () => {
const component = createComponent(<AvatarOverlay account={account} friend={friend} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
render(<AvatarOverlay account={account} friend={friend} />);
expect(screen.queryAllByRole('img')).toHaveLength(2);
});
});

View file

@ -1,12 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import Badge from '../badge';
describe('<Badge />', () => {
it('renders correctly', () => {
const component = renderer.create(<Badge slug='patron' title='Patron' />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render, screen } from '../../jest/test-helpers';
import Badge from '../badge';
describe('<Badge />', () => {
it('renders correctly', () => {
render(<Badge slug='patron' title='Patron' />);
expect(screen.getByTestId('badge')).toHaveTextContent('Patron');
});
});

View file

@ -1,13 +0,0 @@
import React from 'react';
import { createComponent } from 'soapbox/test_helpers';
import ColumnBackButton from '../column_back_button';
describe('<ColumnBackButton />', () => {
it('renders correctly', () => {
const component = createComponent(<ColumnBackButton />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render, screen } from '../../jest/test-helpers';
import ColumnBackButton from '../column_back_button';
describe('<ColumnBackButton />', () => {
it('renders correctly', () => {
render(<ColumnBackButton />);
expect(screen.getByRole('button')).toHaveTextContent('Back');
});
});

View file

@ -1,16 +1,15 @@
import React from 'react';
import { normalizeAccount } from 'soapbox/normalizers';
import { createComponent } from 'soapbox/test_helpers';
import { render, screen } from '../../jest/test-helpers';
import DisplayName from '../display_name';
describe('<DisplayName />', () => {
it('renders display name + account name', () => {
const account = normalizeAccount({ acct: 'bar@baz' });
const component = createComponent(<DisplayName account={account} />);
const tree = component.toJSON();
render(<DisplayName account={account} />);
expect(tree).toMatchSnapshot();
expect(screen.getByTestId('display-name')).toHaveTextContent('bar@baz');
});
});

View file

@ -1,7 +1,6 @@
import React from 'react';
import { createComponent } from 'soapbox/test_helpers';
import { render, screen } from '../../jest/test-helpers';
import EmojiSelector from '../emoji_selector';
describe('<EmojiSelector />', () => {
@ -9,8 +8,8 @@ describe('<EmojiSelector />', () => {
const children = <EmojiSelector />;
children.__proto__.addEventListener = () => {};
const component = createComponent(children, {}, true);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
render(children);
expect(screen.queryAllByRole('button')).toHaveLength(6);
});
});

View file

@ -1,8 +1,7 @@
import React from 'react';
import { defineMessages } from 'react-intl';
import { createComponent } from 'soapbox/test_helpers';
import { render, screen } from '../../jest/test-helpers';
import TimelineQueueButtonHeader from '../timeline_queue_button_header';
const messages = defineMessages({
@ -10,32 +9,35 @@ const messages = defineMessages({
});
describe('<TimelineQueueButtonHeader />', () => {
it('renders correctly', () => {
expect(createComponent(
it('renders correctly', async() => {
render(
<TimelineQueueButtonHeader
key='timeline-queue-button-header'
onClick={() => {}} // eslint-disable-line react/jsx-no-bind
count={0}
message={messages.queue}
/>,
).toJSON()).toMatchSnapshot();
);
expect(screen.queryAllByRole('link')).toHaveLength(0);
expect(createComponent(
render(
<TimelineQueueButtonHeader
key='timeline-queue-button-header'
onClick={() => {}} // eslint-disable-line react/jsx-no-bind
count={1}
message={messages.queue}
/>,
).toJSON()).toMatchSnapshot();
);
expect(screen.getByText('Click to see 1 new post', { hidden: true })).toBeInTheDocument();
expect(createComponent(
render(
<TimelineQueueButtonHeader
key='timeline-queue-button-header'
onClick={() => {}} // eslint-disable-line react/jsx-no-bind
count={9999999}
message={messages.queue}
/>,
).toJSON()).toMatchSnapshot();
);
expect(screen.getByText('Click to see 9999999 new posts', { hidden: true })).toBeInTheDocument();
});
});

View file

@ -28,7 +28,7 @@ export default class AutosuggestEmoji extends React.PureComponent {
}
return (
<div className='autosuggest-emoji'>
<div className='autosuggest-emoji' data-testid='emoji'>
<img
className='emojione'
src={url}

View file

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
const Badge = (props: any) => (
<span className={'badge badge--' + props.slug}>{props.title}</span>
<span data-testid='badge' className={'badge badge--' + props.slug}>{props.title}</span>
);
Badge.propTypes = {

View file

@ -72,7 +72,7 @@ class DisplayName extends React.PureComponent {
}
return (
<span className='display-name'>
<span className='display-name' data-testid='display-name'>
<HoverRefWrapper accountId={account.get('id')} inline>
{displayName}
</HoverRefWrapper>

View file

@ -1,72 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Button /> adds class "button-secondary" if props.theme="secondary" given 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="button"
/>
`;
exports[`<Button /> renders a button element 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="button"
/>
`;
exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all select-none disabled:opacity-50 disabled:cursor-default border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={true}
onClick={[Function]}
type="button"
/>
`;
exports[`<Button /> renders class="button--block" if props.block given 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm flex w-full justify-center"
disabled={false}
onClick={[Function]}
type="button"
/>
`;
exports[`<Button /> renders the children 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="button"
>
<p>
children
</p>
</button>
`;
exports[`<Button /> renders the given text 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="button"
>
foo
</button>
`;
exports[`<Button /> renders the props.text instead of children 1`] = `
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="button"
>
foo
</button>
`;

View file

@ -1,76 +0,0 @@
import { shallow } from 'enzyme';
import React from 'react';
import renderer from 'react-test-renderer';
import Button from '../button';
describe('<Button />', () => {
it('renders a button element', () => {
const component = renderer.create(<Button />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders the given text', () => {
const text = 'foo';
const component = renderer.create(<Button text={text} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('handles click events using the given handler', () => {
const handler = jest.fn();
const button = shallow(<Button onClick={handler} />);
button.find('button').simulate('click');
expect(handler.mock.calls.length).toEqual(1);
});
it('does not handle click events if props.disabled given', () => {
const handler = jest.fn();
const button = shallow(<Button onClick={handler} disabled />);
button.find('button').simulate('click');
expect(handler.mock.calls.length).toEqual(0);
});
it('renders a disabled attribute if props.disabled given', () => {
const component = renderer.create(<Button disabled />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders the children', () => {
const children = <p>children</p>;
const component = renderer.create(<Button>{children}</Button>);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders the props.text instead of children', () => {
const text = 'foo';
const children = <p>children</p>;
const component = renderer.create(<Button text={text}>{children}</Button>);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('renders class="button--block" if props.block given', () => {
const component = renderer.create(<Button block />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('adds class "button-secondary" if props.theme="secondary" given', () => {
const component = renderer.create(<Button theme='secondary' />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,62 @@
import React from 'react';
import { fireEvent, render, screen } from '../../../../jest/test-helpers';
import Button from '../button';
describe('<Button />', () => {
it('renders the given text', () => {
const text = 'foo';
render(<Button text={text} />);
expect(screen.getByRole('button')).toHaveTextContent(text);
});
it('handles click events using the given handler', () => {
const handler = jest.fn();
render(<Button onClick={handler} />);
fireEvent.click(screen.getByRole('button'));
expect(handler.mock.calls.length).toEqual(1);
});
it('does not handle click events if props.disabled given', () => {
const handler = jest.fn();
render(<Button onClick={handler} disabled />);
fireEvent.click(screen.getByRole('button'));
expect(handler.mock.calls.length).toEqual(0);
});
it('renders a disabled attribute if props.disabled given', () => {
render(<Button disabled />);
expect(screen.getByRole('button')).toBeDisabled();
});
it('renders the children', () => {
render(<Button><p>children</p></Button>);
expect(screen.getByRole('button')).toHaveTextContent('children');
});
it('renders the props.text instead of children', () => {
const text = 'foo';
const children = <p>children</p>;
render(<Button text={text}>{children}</Button>);
expect(screen.getByRole('button')).toHaveTextContent('foo');
expect(screen.getByRole('button')).not.toHaveTextContent('children');
});
it('render full-width button if block prop given', () => {
render(<Button block />);
expect(screen.getByRole('button')).toHaveClass('w-full');
});
it('handles Theme properly', () => {
render(<Button theme='secondary' />);
expect(screen.getByRole('button')).toHaveClass('bg-primary-100');
});
});

View file

@ -64,6 +64,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.El
onClick={handleClick}
ref={ref}
type={type}
data-testid='button'
>
{renderIcon()}
{text || children}

View file

@ -1,12 +1,11 @@
import React from 'react';
import { createShallowComponent } from 'soapbox/test_helpers';
import { render, screen } from '../../../../jest/test-helpers';
import { Card, CardBody, CardHeader, CardTitle } from '../card';
describe('<Card />', () => {
it('renders the CardTitle and CardBody', () => {
const component = createShallowComponent(
render(
<Card>
<CardHeader>
<CardTitle title='Card Title' />
@ -18,13 +17,13 @@ describe('<Card />', () => {
</Card>,
);
expect(component.text()).toContain('Card Title');
expect(component.text()).toContain('Card Body');
expect(component.text()).not.toContain('Back');
expect(screen.getByTestId('card-title')).toHaveTextContent('Card Title');
expect(screen.getByTestId('card-body')).toHaveTextContent('Card Body');
expect(screen.queryByTestId('back-button')).not.toBeInTheDocument();
});
it('renders the Back Button', () => {
const component = createShallowComponent(
render(
<Card>
<CardHeader backHref='/'>
<CardTitle title='Card Title' />
@ -32,6 +31,6 @@ describe('<Card />', () => {
</Card>,
);
expect(component.text()).toContain('Back');
expect(screen.getByTestId('back-button')).toBeInTheDocument();
});
});

View file

@ -55,7 +55,7 @@ const CardHeader: React.FC<ICardHeader> = ({ children, backHref, onBackClick }):
return (
<Comp {...backAttributes} className='mr-2 text-gray-900 dark:text-gray-100' aria-label={intl.formatMessage(messages.back)}>
<InlineSVG src={require('@tabler/icons/icons/arrow-left.svg')} className='h-6 w-6' />
<span className='sr-only'>Back</span>
<span className='sr-only' data-testid='back-button'>Back</span>
</Comp>
);
};
@ -74,11 +74,11 @@ interface ICardTitle {
}
const CardTitle = ({ title }: ICardTitle): JSX.Element => (
<Text size='xl' weight='bold' tag='h1'>{title}</Text>
<Text size='xl' weight='bold' tag='h1' data-testid='card-title'>{title}</Text>
);
const CardBody: React.FC = ({ children }): JSX.Element => (
<div>{children}</div>
<div data-testid='card-body'>{children}</div>
);
export { Card, CardHeader, CardTitle, CardBody };

View file

@ -1,40 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Column /> renders correctly with minimal props 1`] = `
<div
className="relative"
column-type="filled"
role="region"
>
<div
className="space-y-4 bg-white dark:bg-slate-800 sm:shadow-lg dark:sm:shadow-inset overflow-hidden p-4 sm:rounded-xl"
>
<div
className="mb-4 flex flex-row items-center"
>
<button
aria-label="Back"
className="mr-2 text-gray-900 dark:text-gray-100"
onClick={[Function]}
>
<svg
id={
Object {
"process": [Function],
}
}
/>
<span
className="sr-only"
>
Back
</span>
</button>
<h1
className="text-xl text-gray-900 dark:text-gray-100 font-bold tracking-normal font-sans"
/>
</div>
<div />
</div>
</div>
`;

View file

@ -1,13 +0,0 @@
import React from 'react';
import { createComponent } from 'soapbox/test_helpers';
import Column from '../column';
describe('<Column />', () => {
it('renders correctly with minimal props', () => {
const component = createComponent(<Column />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

View file

@ -0,0 +1,12 @@
import React from 'react';
import { render, screen } from '../../../../jest/test-helpers';
import Column from '../column';
describe('<Column />', () => {
it('renders correctly with minimal props', () => {
render(<Column />);
expect(screen.getByRole('button')).toHaveTextContent('Back');
});
});

View file

@ -1,61 +0,0 @@
import React from 'react';
import { createShallowComponent } from 'soapbox/test_helpers';
jest.mock('uuid', () => ({
...jest.requireActual('uuid'),
}));
import FormGroup from '../form-group';
describe('<FormGroup />', () => {
it('connects the label and input', () => {
const component = createShallowComponent(
<FormGroup labelText='My label'>
<input type='text' />
</FormGroup>,
);
const otherComponent = createShallowComponent(
<FormGroup labelText='My other label'>
<input type='text' />
</FormGroup>,
);
const inputId = component.find('input').at(0).prop('id');
const labelId = component.find('label').at(0).prop('htmlFor');
expect(inputId).toBe(labelId);
const otherInputId = otherComponent.find('input').at(0).prop('id');
expect(otherInputId).not.toBe(inputId);
});
it('renders errors', () => {
const component = createShallowComponent(
<FormGroup labelText='My label' errors={['is invalid', 'is required']}>
<input type='text' />
</FormGroup>,
);
expect(component.text()).toContain('is invalid, is required');
});
it('renders label', () => {
const component = createShallowComponent(
<FormGroup labelText='My label'>
<input type='text' />
</FormGroup>,
);
expect(component.text()).toContain('My label');
});
it('renders hint', () => {
const component = createShallowComponent(
<FormGroup labelText='My label' hintText='My hint'>
<input type='text' />
</FormGroup>,
);
expect(component.text()).toContain('My hint');
});
});

View file

@ -0,0 +1,60 @@
import React from 'react';
import { render, screen } from '../../../../jest/test-helpers';
import FormGroup from '../form-group';
jest.mock('uuid', () => jest.requireActual('uuid'));
describe('<FormGroup />', () => {
it('connects the label and input', () => {
render(
<>
<div>
<FormGroup labelText='My label'>
<input type='text' data-testid='winner' />
</FormGroup>
</div>
<div>
<FormGroup labelText='My other label'>
<input type='text' />
</FormGroup>
</div>
</>,
);
expect(screen.getByLabelText('My label')).toHaveAttribute('data-testid');
expect(screen.getByLabelText('My other label')).not.toHaveAttribute('data-testid');
expect(screen.queryByTestId('form-group-error')).not.toBeInTheDocument();
});
it('renders errors', () => {
render(
<FormGroup labelText='My label' errors={['is invalid', 'is required']}>
<input type='text' />
</FormGroup>,
);
expect(screen.getByTestId('form-group-error')).toHaveTextContent('is invalid');
});
it('renders label', () => {
render(
<FormGroup labelText='My label'>
<input type='text' />
</FormGroup>,
);
expect(screen.getByTestId('form-group-label')).toHaveTextContent('My label');
});
it('renders hint', () => {
render(
<FormGroup labelText='My label' hintText='My hint'>
<input type='text' />
</FormGroup>,
);
expect(screen.getByTestId('form-group-hint')).toHaveTextContent('My hint');
});
});

View file

@ -24,6 +24,7 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
<div>
<label
htmlFor={formFieldId}
data-testid='form-group-label'
className='block text-sm font-medium text-gray-700 dark:text-gray-400'
>
{labelText}
@ -34,13 +35,16 @@ const FormGroup: React.FC<IFormGroup> = (props) => {
{inputChildren.filter((_, i) => i !== 0)}
{errors?.length > 0 && (
<p className='mt-0.5 text-xs text-danger-900 bg-danger-200 rounded-md inline-block px-2 py-1 relative form-error'>
<p
data-testid='form-group-error'
className='mt-0.5 text-xs text-danger-900 bg-danger-200 rounded-md inline-block px-2 py-1 relative form-error'
>
{errors.join(', ')}
</p>
)}
{hintText ? (
<p className='mt-0.5 text-xs text-gray-400'>
<p data-testid='form-group-hint' className='mt-0.5 text-xs text-gray-400'>
{hintText}
</p>
) : null}

View file

@ -1,40 +0,0 @@
import React from 'react';
import { createShallowComponent } from 'soapbox/test_helpers';
import Form from '../form';
describe('<Form />', () => {
it('renders children', () => {
const onSubmitMock = jest.fn();
const component = createShallowComponent(
<Form onSubmit={onSubmitMock}>children</Form>,
);
expect(component.text()).toContain('children');
});
it('handles onSubmit prop', () => {
const onSubmitMock = jest.fn();
const component = createShallowComponent(
<Form onSubmit={onSubmitMock}>children</Form>,
);
component.find('form').at(0).simulate('submit', {
preventDefault: () => {},
});
expect(onSubmitMock).toHaveBeenCalled();
});
it('handles disabled prop', () => {
const onSubmitMock = jest.fn();
const component = createShallowComponent(
<Form onSubmit={onSubmitMock} disabled>
<button type='submit'>Submit</button>
</Form>,
);
component.find('button').at(0).simulate('click');
expect(onSubmitMock).not.toHaveBeenCalled();
});
});

View file

@ -0,0 +1,29 @@
import React from 'react';
import { fireEvent, render, screen } from '../../../../jest/test-helpers';
import Form from '../form';
describe('<Form />', () => {
it('renders children', () => {
const onSubmitMock = jest.fn();
render(
<Form onSubmit={onSubmitMock}>children</Form>,
);
expect(screen.getByTestId('form')).toHaveTextContent('children');
});
it('handles onSubmit prop', () => {
const onSubmitMock = jest.fn();
render(
<Form onSubmit={onSubmitMock}>children</Form>,
);
fireEvent.submit(
screen.getByTestId('form'), {
preventDefault: () => {},
},
);
expect(onSubmitMock).toHaveBeenCalled();
});
});

View file

@ -1,7 +1,6 @@
import * as React from 'react';
interface IForm {
disabled?: boolean,
onSubmit?: (event: React.FormEvent) => void,
}
@ -15,7 +14,7 @@ const Form: React.FC<IForm> = ({ onSubmit, children, ...filteredProps }) => {
}, [onSubmit]);
return (
<form onSubmit={handleSubmit} className='space-y-4' {...filteredProps}>
<form data-testid='form' onSubmit={handleSubmit} className='space-y-4' {...filteredProps}>
{children}
</form>
);

View file

@ -3,7 +3,7 @@ import React from 'react';
import InlineSVG from 'react-inlinesvg';
import { defineMessages, useIntl } from 'react-intl';
import Icon from '../../icon';
import Icon from '../icon/icon';
import Tooltip from '../tooltip/tooltip';
const messages = defineMessages({

View file

@ -19,7 +19,7 @@ const AuthLayout = () => (
<main className='relative flex flex-col h-screen'>
<header className='pt-10 flex justify-center relative'>
<Link to='/' className='cursor-pointer'>
<img src='/instance/images/truth-logo.svg' alt='Logo' class='h-7' />
<img src='/instance/images/truth-logo.svg' alt='Logo' className='h-7' />
</Link>
</header>

View file

@ -1,27 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<CaptchaField /> renders null by default 1`] = `null`;
exports[`<NativeCaptchaField /> renders correctly 1`] = `
<div
className="captcha"
>
<img
alt="captcha"
src="data:image/png;base64,..."
/>
<div
className="input required"
>
<input
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
onChange={[Function]}
placeholder="Enter the pictured text"
required={true}
type="text"
/>
</div>
</div>
`;

View file

@ -1,267 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LoginForm /> renders for Mastodon 1`] = `
<div>
<div
className="pb-4 sm:pb-10 mb-4 border-b border-gray-200 border-solid -mx-4 sm:-mx-10"
>
<h1
className="text-center font-bold text-2xl"
>
Sign In
</h1>
</div>
<div
className="sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto"
>
<form
className="space-y-4"
onSubmit={[Function]}
>
<div>
<label
className="block text-sm font-medium text-gray-700 dark:text-gray-400"
htmlFor="field-1"
>
Email address
</label>
<div
className="mt-1 dark:text-white"
>
<div
className="mt-1 relative rounded-md shadow-sm"
>
<input
aria-label="Email address"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="dark:bg-slate-800 block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500"
id="field-1"
name="username"
placeholder="Email address"
required={true}
type="text"
/>
</div>
</div>
</div>
<div>
<label
className="block text-sm font-medium text-gray-700 dark:text-gray-400"
htmlFor="field-1"
>
Password
</label>
<div
className="mt-1 dark:text-white"
>
<div
className="mt-1 relative rounded-md shadow-sm"
>
<input
aria-label="Password"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="dark:bg-slate-800 block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500 pr-7"
id="field-1"
name="password"
placeholder="Password"
required={true}
type="password"
/>
<div
className="absolute inset-y-0 right-0 flex items-center"
data-reach-tooltip-trigger=""
data-state="tooltip-hidden"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onMouseMove={[Function]}
onPointerDown={[Function]}
onPointerEnter={[Function]}
onPointerLeave={[Function]}
onPointerMove={[Function]}
>
<button
className="text-gray-400 hover:text-gray-500 h-full px-2 focus:ring-primary-500 focus:ring-2"
onClick={[Function]}
tabIndex={-1}
type="button"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</button>
</div>
</div>
<p
className="mt-0.5 text-xs text-gray-400"
>
<a
className="hover:underline"
href="/reset-password"
onClick={[Function]}
>
Trouble logging in?
</a>
</p>
</div>
</div>
<div
className="flex justify-end space-x-2"
>
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="submit"
>
Sign in
</button>
</div>
</form>
</div>
</div>
`;
exports[`<LoginForm /> renders for Pleroma 1`] = `
<div>
<div
className="pb-4 sm:pb-10 mb-4 border-b border-gray-200 border-solid -mx-4 sm:-mx-10"
>
<h1
className="text-center font-bold text-2xl"
>
Sign In
</h1>
</div>
<div
className="sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto"
>
<form
className="space-y-4"
onSubmit={[Function]}
>
<div>
<label
className="block text-sm font-medium text-gray-700 dark:text-gray-400"
htmlFor="field-1"
>
Email address
</label>
<div
className="mt-1 dark:text-white"
>
<div
className="mt-1 relative rounded-md shadow-sm"
>
<input
aria-label="Email address"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="dark:bg-slate-800 block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500"
id="field-1"
name="username"
placeholder="Email address"
required={true}
type="text"
/>
</div>
</div>
</div>
<div>
<label
className="block text-sm font-medium text-gray-700 dark:text-gray-400"
htmlFor="field-1"
>
Password
</label>
<div
className="mt-1 dark:text-white"
>
<div
className="mt-1 relative rounded-md shadow-sm"
>
<input
aria-label="Password"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="dark:bg-slate-800 block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500 pr-7"
id="field-1"
name="password"
placeholder="Password"
required={true}
type="password"
/>
<div
className="absolute inset-y-0 right-0 flex items-center"
data-reach-tooltip-trigger=""
data-state="tooltip-hidden"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onMouseMove={[Function]}
onPointerDown={[Function]}
onPointerEnter={[Function]}
onPointerLeave={[Function]}
onPointerMove={[Function]}
>
<button
className="text-gray-400 hover:text-gray-500 h-full px-2 focus:ring-primary-500 focus:ring-2"
onClick={[Function]}
tabIndex={-1}
type="button"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</button>
</div>
</div>
<p
className="mt-0.5 text-xs text-gray-400"
>
<a
className="hover:underline"
href="/reset-password"
onClick={[Function]}
>
Trouble logging in?
</a>
</p>
</div>
</div>
<div
className="flex justify-end space-x-2"
>
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="submit"
>
Sign in
</button>
</div>
</form>
</div>
</div>
`;

View file

@ -1,135 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LoginPage /> renders correctly on load 1`] = `
<div>
<div
className="pb-4 sm:pb-10 mb-4 border-b border-gray-200 border-solid -mx-4 sm:-mx-10"
>
<h1
className="text-center font-bold text-2xl"
>
Sign In
</h1>
</div>
<div
className="sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto"
>
<form
className="space-y-4"
disabled={false}
onSubmit={[Function]}
>
<div>
<label
className="block text-sm font-medium text-gray-700 dark:text-gray-400"
htmlFor="field-1"
>
Email address
</label>
<div
className="mt-1 dark:text-white"
>
<div
className="mt-1 relative rounded-md shadow-sm"
>
<input
aria-label="Email address"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="dark:bg-slate-800 block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500"
id="field-1"
name="username"
placeholder="Email address"
required={true}
type="text"
/>
</div>
</div>
</div>
<div>
<label
className="block text-sm font-medium text-gray-700 dark:text-gray-400"
htmlFor="field-1"
>
Password
</label>
<div
className="mt-1 dark:text-white"
>
<div
className="mt-1 relative rounded-md shadow-sm"
>
<input
aria-label="Password"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="dark:bg-slate-800 block w-full sm:text-sm border-gray-300 dark:border-gray-600 rounded-md focus:ring-indigo-500 focus:border-indigo-500 pr-7"
id="field-1"
name="password"
placeholder="Password"
required={true}
type="password"
/>
<div
className="absolute inset-y-0 right-0 flex items-center"
data-reach-tooltip-trigger=""
data-state="tooltip-hidden"
onBlur={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
onMouseMove={[Function]}
onPointerDown={[Function]}
onPointerEnter={[Function]}
onPointerLeave={[Function]}
onPointerMove={[Function]}
>
<button
className="text-gray-400 hover:text-gray-500 h-full px-2 focus:ring-primary-500 focus:ring-2"
onClick={[Function]}
tabIndex={-1}
type="button"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</button>
</div>
</div>
<p
className="mt-0.5 text-xs text-gray-400"
>
<a
className="hover:underline"
href="/reset-password"
onClick={[Function]}
>
Trouble logging in?
</a>
</p>
</div>
</div>
<div
className="flex justify-end space-x-2"
>
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 focus:ring-2 focus:ring-offset-2 px-4 py-2 text-sm"
disabled={false}
onClick={[Function]}
type="submit"
>
Sign in
</button>
</div>
</form>
</div>
</div>
`;

View file

@ -1,16 +1,14 @@
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import renderer from 'react-test-renderer';
import { createComponent } from 'soapbox/test_helpers';
import { render, screen } from '../../../../jest/test-helpers';
import CaptchaField, { NativeCaptchaField } from '../captcha';
describe('<CaptchaField />', () => {
it('renders null by default', () => {
expect(createComponent(
<CaptchaField />,
).toJSON()).toMatchSnapshot();
render(<CaptchaField />);
expect(screen.queryAllByRole('textbox')).toHaveLength(0);
});
});
@ -23,11 +21,13 @@ describe('<NativeCaptchaField />', () => {
url: 'data:image/png;base64,...',
});
expect(renderer.create(
render(
<NativeCaptchaField
captcha={captcha}
onChange={() => {}} // eslint-disable-line react/jsx-no-bind
/>,
).toJSON()).toMatchSnapshot();
);
expect(screen.queryAllByRole('textbox')).toHaveLength(1);
});
});

View file

@ -1,31 +0,0 @@
import React from 'react';
import rootReducer from 'soapbox/reducers';
import { createComponent, mockStore } from 'soapbox/test_helpers';
import LoginForm from '../login_form';
describe('<LoginForm />', () => {
it('renders for Pleroma', () => {
const state = rootReducer(undefined, {})
.update('instance', instance => instance.set('version', '2.7.2 (compatible; Pleroma 2.3.0)'));
const store = mockStore(state);
expect(createComponent(
<LoginForm />,
{ store },
).toJSON()).toMatchSnapshot();
});
it('renders for Mastodon', () => {
const state = rootReducer(undefined, {})
.update('instance', instance => instance.set('version', '3.0.0'));
const store = mockStore(state);
expect(createComponent(
<LoginForm />,
{ store },
).toJSON()).toMatchSnapshot();
});
});

View file

@ -0,0 +1,31 @@
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import { render, screen } from '../../../../jest/test-helpers';
import LoginForm from '../login_form';
describe('<LoginForm />', () => {
it('renders for Pleroma', () => {
const store = {
instance: ImmutableMap({
version: '2.7.2 (compatible; Pleroma 2.3.0)',
}),
};
render(<LoginForm />, null, store);
expect(screen.getByRole('heading')).toHaveTextContent('Sign In');
});
it('renders for Mastodon', () => {
const store = {
instance: ImmutableMap({
version: '3.0.0',
}),
};
render(<LoginForm />, null, store);
expect(screen.getByRole('heading')).toHaveTextContent('Sign In');
});
});

View file

@ -1,21 +1,20 @@
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import rootReducer from 'soapbox/reducers';
import { createComponent, mockStore } from 'soapbox/test_helpers';
import { render, screen } from '../../../../jest/test-helpers';
import LoginPage from '../login_page';
describe('<LoginPage />', () => {
it('renders correctly on load', () => {
const state = rootReducer(undefined, {})
.set('me', '1234')
.update('instance', instance => instance.set('version', '2.7.2 (compatible; Pleroma 2.3.0)'));
const store = mockStore(state);
const store = {
instance: ImmutableMap({
version: '2.7.2 (compatible; Pleroma 2.3.0)',
}),
};
expect(createComponent(
<LoginPage />,
{ store },
).toJSON()).toMatchSnapshot();
render(<LoginPage />, null, store);
expect(screen.getByRole('heading')).toHaveTextContent('Sign In');
});
// it('renders the OTP form when logIn returns with mfa_required', () => {

View file

@ -1,19 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import { createShallowComponent, mockStore } from 'soapbox/test_helpers';
import OtpAuthForm from '../otp_auth_form';
describe('<OtpAuthForm />', () => {
it('renders correctly', () => {
const store = mockStore(ImmutableMap({ mfa_auth_needed: true }));
const component = createShallowComponent(
<OtpAuthForm mfa_token={'12345'} />,
{ store },
);
expect(component.text()).toContain('OTP Login');
expect(component.exists('form')).toBe(true);
});
});

View file

@ -0,0 +1,13 @@
import React from 'react';
import { render, screen } from '../../../../jest/test-helpers';
import OtpAuthForm from '../otp_auth_form';
describe('<OtpAuthForm />', () => {
it('renders correctly', () => {
render(<OtpAuthForm mfa_token={'12345'} />);
expect(screen.getByRole('heading')).toHaveTextContent('OTP Login');
expect(screen.getByTestId('form')).toBeInTheDocument();
});
});

View file

@ -1,17 +1,15 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { Route, Switch } from 'react-router-dom';
import { __stub } from 'soapbox/api';
import rootReducer from 'soapbox/reducers';
import { createShallowComponent, mockStore } from 'soapbox/test_helpers';
import { fireEvent, render, screen, waitFor } from '../../../../jest/test-helpers';
import PasswordResetConfirm from '../password_reset_confirm';
const TestableComponent = () => (
<Switch>
<Route path='/edit' exact><PasswordResetConfirm /></Route>
<Route path='/' exact><span>Homepage</span></Route>
<Route path='/' exact><span data-testid='home'>Homepage</span></Route>
</Switch>
);
@ -22,24 +20,23 @@ describe('<PasswordResetConfirm />', () => {
.reply(200, {});
});
const state = rootReducer(undefined, {});
const store = mockStore(state);
const component = createShallowComponent(
render(
<TestableComponent />,
{ store },
{},
null,
{ initialEntries: ['/edit'] },
);
await component.find('form').at(0).simulate('submit', {
preventDefault: () => {},
});
await act(async() => {
await new Promise(resolve => setTimeout(resolve, 0));
component.update();
});
fireEvent.submit(
screen.getByTestId('form'), {
preventDefault: () => {},
},
);
expect(component.text()).toContain('Homepage');
expect(component.text()).not.toContain('Expired token');
await waitFor(() => {
expect(screen.getByTestId('home')).toHaveTextContent('Homepage');
expect(screen.queryByTestId('form-group-error')).not.toBeInTheDocument();
});
});
it('handles failed responses from the API', async() => {
@ -48,23 +45,22 @@ describe('<PasswordResetConfirm />', () => {
.reply(403, {});
});
const state = rootReducer(undefined, {});
const store = mockStore(state);
const component = createShallowComponent(
render(
<TestableComponent />,
{ store },
{},
null,
{ initialEntries: ['/edit'] },
);
await component.find('form').at(0).simulate('submit', {
preventDefault: () => {},
});
await act(async() => {
await new Promise(resolve => setTimeout(resolve, 0));
component.update();
});
await fireEvent.submit(
screen.getByTestId('form'), {
preventDefault: () => {},
},
);
expect(component.text()).toContain('Expired token');
expect(component.text()).not.toContain('Homepage');
await waitFor(() => {
expect(screen.queryByTestId('home')).not.toBeInTheDocument();
expect(screen.queryByTestId('form-group-error')).toBeInTheDocument();
});
});
});

View file

@ -30,7 +30,7 @@ const LoginForm = ({ isLoading, handleSubmit }) => {
</div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={handleSubmit} disabled={isLoading}>
<Form onSubmit={handleSubmit}>
<FormGroup labelText={intl.formatMessage(messages.email)}>
<Input
aria-label={intl.formatMessage(messages.email)}

View file

@ -66,7 +66,7 @@ class OtpAuthForm extends ImmutablePureComponent {
</div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={this.handleSubmit} disabled={this.state.isLoading}>
<Form onSubmit={this.handleSubmit}>
<FormGroup
labelText={intl.formatMessage(messages.otpCodeLabel)}
hintText={intl.formatMessage(messages.otpCodeHint)}

View file

@ -49,7 +49,7 @@ class PasswordReset extends ImmutablePureComponent {
</div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={this.handleSubmit} disabled={this.state.isLoading}>
<Form onSubmit={this.handleSubmit}>
<FormGroup labelText={intl.formatMessage(messages.nicknameOrEmail)}>
<Input
name='nickname_or_email'

View file

@ -62,7 +62,7 @@ const PasswordResetConfirm = ({ resetPasswordConfirm }) => {
</div>
<div className='sm:pt-10 sm:w-2/3 md:w-1/2 mx-auto'>
<Form onSubmit={handleSubmit} disabled={isLoading}>
<Form onSubmit={handleSubmit}>
<FormGroup labelText='Password' errors={renderErrors()}>
<Input
type='password'

View file

@ -53,7 +53,7 @@ const DeleteAccount = () => {
{intl.formatMessage(messages.deleteText)}
</p>
<Form onSubmit={handleSubmit} disabled={isLoading}>
<Form onSubmit={handleSubmit}>
<FormGroup labelText={intl.formatMessage(messages.passwordFieldLabel)}>
<Input
type='password'

View file

@ -183,7 +183,7 @@ class CreateApp extends ImmutablePureComponent {
return (
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
<Form onSubmit={this.handleSubmit} disabled={isLoading}>
<Form onSubmit={this.handleSubmit}>
<FormGroup labelText={<FormattedMessage id='app_create.name_label' defaultMessage='App name' />}>
<Input
placeholder={intl.formatMessage(messages.namePlaceholder)}
@ -220,7 +220,7 @@ class CreateApp extends ImmutablePureComponent {
</FormGroup>
<FormActions>
<Button theme='primary' type='submit'>
<Button theme='primary' type='submit' disabled={isLoading}>
<FormattedMessage id='app_create.submit' defaultMessage='Create app' />
</Button>
</FormActions>

View file

@ -90,7 +90,7 @@ class SettingsStore extends ImmutablePureComponent {
return (
<Column label={intl.formatMessage(messages.heading)} backHref='/developers'>
<Form onSubmit={this.handleSubmit} disabled={!jsonValid || isLoading}>
<Form onSubmit={this.handleSubmit}>
<FormGroup
hintText={intl.formatMessage(messages.hint)}
errors={jsonValid ? [] : ['is invalid']}

View file

@ -60,7 +60,7 @@ const EditEmail = () => {
</CardHeader>
<CardBody>
<Form onSubmit={handleSubmit} disabled={isLoading}>
<Form onSubmit={handleSubmit}>
<FormGroup labelText={intl.formatMessage(messages.emailFieldLabel)}>
<Input
placeholder={intl.formatMessage({ id: 'edit_email.placeholder', defaultMessage: 'me@example.com' })}

View file

@ -59,7 +59,7 @@ const EditPassword = () => {
</CardHeader>
<CardBody>
<Form onSubmit={handleSubmit} disabled={isLoading}>
<Form onSubmit={handleSubmit}>
<FormGroup labelText={intl.formatMessage(messages.oldPasswordFieldLabel)}>
<Input
type='password'

View file

@ -1,103 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Checkbox /> renders correctly 1`] = `
<div
className="input boolean"
>
<input
type="checkbox"
/>
</div>
`;
exports[`<FieldsGroup /> renders correctly 1`] = `
<div
className="fields-group"
/>
`;
exports[`<FileChooser /> renders correctly 1`] = `
<div
className="input"
>
<input
accept={
Array [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
]
}
type="file"
/>
</div>
`;
exports[`<InputContainer /> renders correctly 1`] = `
<div
className="input"
/>
`;
exports[`<RadioGroup /> renders correctly 1`] = `
<div
className="input with_floating_label radio_buttons"
>
<div
className="label_input"
>
<label />
<ul />
</div>
</div>
`;
exports[`<SelectDropdown /> renders correctly 1`] = `
<select
className="pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
>
<option
value="one"
>
One
</option>
<option
value="two"
>
Two
</option>
<option
value="three"
>
Three
</option>
</select>
`;
exports[`<SimpleForm /> renders correctly 1`] = `
<form
acceptCharset="UTF-8"
className="simple_form"
method="post"
onSubmit={[Function]}
/>
`;
exports[`<SimpleInput /> renders correctly 1`] = `
<div
className="input"
>
<input />
</div>
`;
exports[`<TextInput /> renders correctly 1`] = `
<div
className="input"
>
<input
type="text"
/>
</div>
`;

View file

@ -1,86 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import {
InputContainer,
SimpleInput,
SimpleForm,
FieldsGroup,
Checkbox,
RadioGroup,
SelectDropdown,
TextInput,
FileChooser,
} from '..';
describe('<InputContainer />', () => {
it('renders correctly', () => {
expect(renderer.create(
<InputContainer />,
).toJSON()).toMatchSnapshot();
});
});
describe('<SimpleInput />', () => {
it('renders correctly', () => {
expect(renderer.create(
<SimpleInput />,
).toJSON()).toMatchSnapshot();
});
});
describe('<SimpleForm />', () => {
it('renders correctly', () => {
expect(renderer.create(
<SimpleForm />,
).toJSON()).toMatchSnapshot();
});
});
describe('<FieldsGroup />', () => {
it('renders correctly', () => {
expect(renderer.create(
<FieldsGroup />,
).toJSON()).toMatchSnapshot();
});
});
describe('<Checkbox />', () => {
it('renders correctly', () => {
expect(renderer.create(
<Checkbox />,
).toJSON()).toMatchSnapshot();
});
});
describe('<RadioGroup />', () => {
it('renders correctly', () => {
expect(renderer.create(
<RadioGroup />,
).toJSON()).toMatchSnapshot();
});
});
describe('<SelectDropdown />', () => {
it('renders correctly', () => {
expect(renderer.create(
<SelectDropdown items={{ one: 'One', two: 'Two', three: 'Three' }} />,
).toJSON()).toMatchSnapshot();
});
});
describe('<TextInput />', () => {
it('renders correctly', () => {
expect(renderer.create(
<TextInput />,
).toJSON()).toMatchSnapshot();
});
});
describe('<FileChooser />', () => {
it('renders correctly', () => {
expect(renderer.create(
<FileChooser />,
).toJSON()).toMatchSnapshot();
});
});

View file

@ -144,7 +144,7 @@ class DisableOtpForm extends ImmutablePureComponent {
const { isLoading, password } = this.state;
return (
<Form onSubmit={this.handleSubmit} disabled={isLoading}>
<Form onSubmit={this.handleSubmit}>
<Stack>
<Text weight='medium'>
<FormattedMessage id='mfa.otp_enabled_title' defaultMessage='OTP Enabled' />
@ -335,7 +335,7 @@ class OtpConfirmForm extends ImmutablePureComponent {
<Stack space={4}>
<hr className='mt-4' />
<Form onSubmit={this.handleSubmit} disabled={isLoading}>
<Form onSubmit={this.handleSubmit}>
<Stack>
<Text weight='semibold' size='lg'>
1. <FormattedMessage id='mfa.mfa_setup_scan_title' defaultMessage='Scan' />

View file

@ -1,29 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ComposeButton /> renders a button element 1`] = `
<div
className="mt-4"
>
<button
className="inline-flex items-center border font-medium rounded-full focus:outline-none appearance-none transition-all border-transparent text-white bg-accent-500 hover:bg-accent-300 focus:ring-pink-500 focus:ring-2 focus:ring-offset-2 px-6 py-3 text-base flex w-full justify-center"
disabled={false}
onClick={[Function]}
type="button"
>
<div
className="svg-icon mr-2"
>
<svg
id={
Object {
"process": [Function],
}
}
/>
</div>
<span>
Compose
</span>
</button>
</div>
`;

View file

@ -1,26 +1,39 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { Map as ImmutableMap } from 'immutable';
import React from 'react';
import { IntlProvider } from 'react-intl';
import { Provider } from 'react-redux';
import '@testing-library/jest-dom';
import { MODAL_OPEN } from 'soapbox/actions/modals';
import { mockStore } from 'soapbox/jest/test-helpers';
import rootReducer from 'soapbox/reducers';
import { createComponent, mockStore } from 'soapbox/test_helpers';
import ComposeButton from '../compose-button';
const store = mockStore(rootReducer(ImmutableMap(), {}));
const renderComposeButton = () => {
render(
<Provider store={store}>
<IntlProvider locale='en'>
<ComposeButton />
</IntlProvider>
</Provider>,
);
};
describe('<ComposeButton />', () => {
it('renders a button element', () => {
const component = createComponent(<ComposeButton />);
const tree = component.toJSON();
renderComposeButton();
expect(tree).toMatchSnapshot();
expect(screen.getByRole('button')).toHaveTextContent('Compose');
});
it('dispatches the MODAL_OPEN action', () => {
const store = mockStore(rootReducer(ImmutableMap(), {}));
const component = createComponent(<ComposeButton />, { store });
renderComposeButton();
expect(store.getActions().length).toEqual(0);
component.root.findByType('button').props.onClick();
fireEvent.click(screen.getByRole('button'));
expect(store.getActions()[0].type).toEqual(MODAL_OPEN);
});
});

View file

@ -8,7 +8,7 @@ import { dismissAlert } from '../../../actions/alerts';
import { getAlerts } from '../../../selectors';
const CustomNotificationStack = (props) => (
<div role='assertive' className='z-1000 fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start'>
<div role='assertive' data-testid='toast' className='z-1000 fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start'>
<NotificationStack {...props} />
</div>
);

View file

@ -5,7 +5,7 @@ import {
ALERT_DISMISS,
ALERT_CLEAR,
} from 'soapbox/actions/alerts';
import { applyActions } from 'soapbox/test_helpers';
import { applyActions } from 'soapbox/jest/test-helpers';
import reducer from '../alerts';

View file

@ -28,7 +28,7 @@ import {
NOTIFICATIONS_MARK_READ_REQUEST,
} from 'soapbox/actions/notifications';
import { TIMELINE_DELETE } from 'soapbox/actions/timelines';
import { applyActions } from 'soapbox/test_helpers';
import { applyActions } from 'soapbox/jest/test-helpers';
import reducer from '../notifications';