kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge remote-tracking branch 'soapbox/next' into next_
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>next-virtuoso-proof
commit
e4908fe2b2
|
@ -125,7 +125,7 @@ const Account = ({
|
|||
const LinkEl: any = showProfileHoverCard ? Link : 'div';
|
||||
|
||||
return (
|
||||
<div className='flex-shrink-0 group block w-full overflow-hidden' ref={overflowRef}>
|
||||
<div data-testid='account' className='flex-shrink-0 group block w-full overflow-hidden' ref={overflowRef}>
|
||||
<HStack alignItems={actionAlignment} justifyContent='between'>
|
||||
<HStack alignItems='center' space={3} grow>
|
||||
<ProfilePopper
|
||||
|
|
|
@ -289,6 +289,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
|||
aria-autocomplete='list'
|
||||
id={id}
|
||||
maxLength={maxLength}
|
||||
data-testid='autosuggest-input'
|
||||
/>
|
||||
|
||||
<div className={classNames({
|
||||
|
|
|
@ -16,7 +16,7 @@ const Hashtag = ({ hashtag }) => {
|
|||
const brandColor = useSelector((state) => getSoapboxConfig(state).get('brandColor'));
|
||||
|
||||
return (
|
||||
<HStack alignItems='center' justifyContent='between'>
|
||||
<HStack alignItems='center' justifyContent='between' data-testid='hashtag'>
|
||||
<Stack>
|
||||
<Permalink href={hashtag.get('url')} to={`/tags/${hashtag.get('name')}`} className='hover:underline'>
|
||||
<Text tag='span' size='sm' weight='semibold'>#{hashtag.get('name')}</Text>
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import Emoji from '../emoji';
|
||||
|
||||
describe('<Emoji />', () => {
|
||||
it('renders the given text', () => {
|
||||
render(<Emoji emoji='smile' />);
|
||||
it('renders a simple emoji', () => {
|
||||
render(<Emoji emoji='😀' />);
|
||||
|
||||
expect(screen.getByRole('img').getAttribute('alt')).toBe('smile');
|
||||
const img = screen.getByRole('img');
|
||||
expect(img.getAttribute('src')).toBe('/packs/emoji/1f600.svg');
|
||||
expect(img.getAttribute('alt')).toBe('😀');
|
||||
});
|
||||
|
||||
// https://emojipedia.org/emoji-flag-sequence/
|
||||
it('renders a sequence emoji', () => {
|
||||
render(<Emoji emoji='🇺🇸' />);
|
||||
|
||||
const img = screen.getByRole('img');
|
||||
expect(img.getAttribute('src')).toBe('/packs/emoji/1f1fa-1f1f8.svg');
|
||||
expect(img.getAttribute('alt')).toBe('🇺🇸');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,12 +29,12 @@ const toCodePoints = (unicodeSurrogates: string): string[] => {
|
|||
return points;
|
||||
};
|
||||
|
||||
interface IEmoji {
|
||||
className?: string,
|
||||
interface IEmoji extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
emoji: string,
|
||||
}
|
||||
|
||||
const Emoji: React.FC<IEmoji> = ({ className, emoji }): JSX.Element | null => {
|
||||
const Emoji: React.FC<IEmoji> = (props): JSX.Element | null => {
|
||||
const { emoji, alt, ...rest } = props;
|
||||
const codepoints = toCodePoints(removeVS16s(emoji));
|
||||
const filename = codepoints.join('-');
|
||||
|
||||
|
@ -43,9 +43,9 @@ const Emoji: React.FC<IEmoji> = ({ className, emoji }): JSX.Element | null => {
|
|||
return (
|
||||
<img
|
||||
draggable='false'
|
||||
className={className}
|
||||
alt={emoji}
|
||||
alt={alt || emoji}
|
||||
src={joinPublicPath(`packs/emoji/${filename}.svg`)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import FormActions from '../form-actions';
|
||||
|
||||
describe('<FormActions />', () => {
|
||||
it('renders successfully', () => {
|
||||
render(<FormActions><div data-testid='child'>child</div></FormActions>);
|
||||
|
||||
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -22,6 +22,7 @@ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef
|
|||
'bg-white dark:bg-transparent': !transparent,
|
||||
}, className)}
|
||||
{...filteredProps}
|
||||
data-testid='icon-button'
|
||||
>
|
||||
<InlineSVG src={src} className={iconClassName} />
|
||||
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import Modal from '../modal';
|
||||
|
||||
describe('<Modal />', () => {
|
||||
it('renders', () => {
|
||||
render(<Modal title='Modal title' />);
|
||||
expect(screen.getByTestId('modal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders children', () => {
|
||||
render(<Modal title='Modal title'><div data-testid='child' /></Modal>);
|
||||
expect(screen.getByTestId('child')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('focuses the primary action', () => {
|
||||
render(
|
||||
<Modal
|
||||
title='Modal title'
|
||||
confirmationAction={() => null}
|
||||
confirmationText='Click me'
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button')).toHaveFocus();
|
||||
});
|
||||
|
||||
describe('onClose prop', () => {
|
||||
it('renders the Icon to close the modal', async() => {
|
||||
const mockFn = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<Modal title='Modal title' onClose={mockFn} />);
|
||||
expect(screen.getByTestId('icon-button')).toBeInTheDocument();
|
||||
|
||||
expect(mockFn).not.toBeCalled();
|
||||
await user.click(screen.getByTestId('icon-button'));
|
||||
expect(mockFn).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not render the Icon to close the modal', () => {
|
||||
render(<Modal title='Modal title' />);
|
||||
expect(screen.queryAllByTestId('icon-button')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('confirmationAction prop', () => {
|
||||
it('renders the confirmation button', async() => {
|
||||
const mockFn = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Modal
|
||||
title='Modal title'
|
||||
confirmationAction={mockFn}
|
||||
confirmationText='Click me'
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(mockFn).not.toBeCalled();
|
||||
await user.click(screen.getByRole('button'));
|
||||
expect(mockFn).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not render the actions to', () => {
|
||||
render(<Modal title='Modal title' />);
|
||||
expect(screen.queryAllByTestId('modal-actions')).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('with secondaryAction', () => {
|
||||
it('renders the secondary button', async() => {
|
||||
const confirmationAction = jest.fn();
|
||||
const secondaryAction = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Modal
|
||||
title='Modal title'
|
||||
confirmationAction={confirmationAction}
|
||||
confirmationText='Primary'
|
||||
secondaryAction={secondaryAction}
|
||||
secondaryText='Secondary'
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByText(/secondary/i));
|
||||
expect(secondaryAction).toBeCalled();
|
||||
expect(confirmationAction).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not render the secondary action', () => {
|
||||
render(
|
||||
<Modal
|
||||
title='Modal title'
|
||||
confirmationAction={() => null}
|
||||
confirmationText='Click me'
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryAllByRole('button')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with cancelAction', () => {
|
||||
it('renders the cancel button', async() => {
|
||||
const confirmationAction = jest.fn();
|
||||
const cancelAction = jest.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
<Modal
|
||||
title='Modal title'
|
||||
confirmationAction={confirmationAction}
|
||||
confirmationText='Primary'
|
||||
secondaryAction={cancelAction}
|
||||
secondaryText='Cancel'
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByText(/cancel/i));
|
||||
expect(cancelAction).toBeCalled();
|
||||
expect(confirmationAction).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not render the cancel action', () => {
|
||||
render(
|
||||
<Modal
|
||||
title='Modal title'
|
||||
confirmationAction={() => null}
|
||||
confirmationText='Click me'
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryAllByRole('button')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,7 +17,7 @@ interface IModal {
|
|||
confirmationDisabled?: boolean,
|
||||
confirmationText?: string,
|
||||
confirmationTheme?: 'danger',
|
||||
onClose: () => void,
|
||||
onClose?: () => void,
|
||||
secondaryAction?: () => void,
|
||||
secondaryText?: string,
|
||||
title: string | React.ReactNode,
|
||||
|
@ -46,7 +46,7 @@ const Modal: React.FC<IModal> = ({
|
|||
}, [buttonRef]);
|
||||
|
||||
return (
|
||||
<div className='block w-full max-w-xl p-6 mx-auto overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-slate-800 text-black dark:text-white shadow-xl rounded-2xl pointer-events-auto'>
|
||||
<div data-testid='modal' className='block w-full max-w-xl p-6 mx-auto overflow-hidden text-left align-middle transition-all transform bg-white dark:bg-slate-800 text-black dark:text-white shadow-xl rounded-2xl pointer-events-auto'>
|
||||
<div className='sm:flex sm:items-start w-full justify-between'>
|
||||
<div className='w-full'>
|
||||
<div className='w-full flex flex-row justify-between items-center'>
|
||||
|
@ -71,7 +71,7 @@ const Modal: React.FC<IModal> = ({
|
|||
</div>
|
||||
|
||||
{confirmationAction && (
|
||||
<div className='mt-5 flex flex-row justify-between'>
|
||||
<div className='mt-5 flex flex-row justify-between' data-testid='modal-actions'>
|
||||
<div className='flex-grow'>
|
||||
{cancelAction && (
|
||||
<Button
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import Search from '../search';
|
||||
|
||||
describe('<Search />', () => {
|
||||
it('successfully renders', async() => {
|
||||
render(<Search autosuggest />);
|
||||
expect(screen.getByLabelText('Search')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles onChange', async() => {
|
||||
__stub(mock => {
|
||||
mock.onGet('/api/v1/accounts/search').reply(200, [{ id: 1 }]);
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<Search autosuggest />);
|
||||
|
||||
await user.type(screen.getByLabelText('Search'), '@jus');
|
||||
|
||||
expect(screen.getByLabelText('Search')).toHaveValue('@jus');
|
||||
expect(screen.getByTestId('account')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import React from 'react';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
import TrendsPanel from '../trends-panel';
|
||||
|
||||
describe('<TrendsPanel />', () => {
|
||||
it('renders trending hashtags', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([{
|
||||
name: 'hashtag 1',
|
||||
history: [{ accounts: [] }],
|
||||
}]),
|
||||
}),
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={1} />, null, store);
|
||||
expect(screen.getByTestId('hashtag')).toHaveTextContent(/hashtag 1/i);
|
||||
});
|
||||
|
||||
it('renders multiple trends', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([
|
||||
{
|
||||
name: 'hashtag 1',
|
||||
history: [{ accounts: [] }],
|
||||
},
|
||||
{
|
||||
name: 'hashtag 2',
|
||||
history: [{ accounts: [] }],
|
||||
},
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={3} />, null, store);
|
||||
expect(screen.queryAllByTestId('hashtag')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('respects the limit prop', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([
|
||||
{
|
||||
name: 'hashtag 1',
|
||||
history: [{ accounts: [] }],
|
||||
},
|
||||
{
|
||||
name: 'hashtag 2',
|
||||
history: [{ accounts: [] }],
|
||||
},
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={1} />, null, store);
|
||||
expect(screen.queryAllByTestId('hashtag')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders empty', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([]),
|
||||
}),
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={1} />, null, store);
|
||||
expect(screen.queryAllByTestId('hashtag')).toHaveLength(0);
|
||||
});
|
||||
});
|
|
@ -8,3 +8,6 @@ afterEach(() => clearApiMocks());
|
|||
|
||||
// Mock external dependencies
|
||||
jest.mock('uuid', () => ({ v4: jest.fn(() => 1) }));
|
||||
|
||||
const intersectionObserverMock = () => ({ observe: () => null, disconnect: () => null });
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
|
||||
|
|
Ładowanie…
Reference in New Issue