diff --git a/frontend/app/components/sort-picker.spec.tsx b/frontend/app/components/sort-picker.spec.tsx new file mode 100644 index 00000000..6c4fe4cb --- /dev/null +++ b/frontend/app/components/sort-picker.spec.tsx @@ -0,0 +1,50 @@ +import '@testing-library/jest-dom'; +import { fireEvent, waitFor } from '@testing-library/preact'; +import { h } from 'preact'; + +import { render } from 'tests/utils'; +import * as commentsActions from 'store/comments/actions'; +import type { StoreState } from 'store'; + +import { SortPicker } from './sort-picker'; + +const defaultState = { comments: {} as StoreState['comments'], hiddenUsers: {} }; +const stateWithSort = { comments: { sort: '-active' } as StoreState['comments'] }; + +describe('', () => { + it('should render sort picker with options', () => { + const { container, queryAllByText, queryByText } = render(, defaultState); + + expect(container.querySelectorAll('option')).toHaveLength(8); + expect(queryAllByText('Best')).toHaveLength(2); + expect(queryByText('Sort by')).toBeInTheDocument(); + }); + + it('should has static class names', () => { + const { container } = render(, defaultState); + + expect(container.querySelector('.sort-picker')).toBeInTheDocument(); + }); + + it('should render selected element', () => { + const { container, queryAllByText } = render(, stateWithSort); + + expect(queryAllByText('Recently updated')).toHaveLength(2); + expect(container.querySelector('[value="-active"]')?.selected).toBeTruthy(); + }); + + it('should change selected store', async () => { + const nextOption = '-controversy'; + const updateSorting = jest.spyOn(commentsActions, 'updateSorting'); + const { container } = render(, defaultState); + const select = container.querySelector('select') as HTMLSelectElement; + + expect(select).toBeInTheDocument(); + + fireEvent.change(select, { target: { value: nextOption } }); + + await waitFor(() => expect(updateSorting).toHaveBeenCalledWith(nextOption)); + + expect(container.querySelector(`[value="${nextOption}"]`)?.selected).toBeTruthy(); + }); +}); diff --git a/frontend/app/components/sort-picker.tsx b/frontend/app/components/sort-picker.tsx new file mode 100644 index 00000000..2e639154 --- /dev/null +++ b/frontend/app/components/sort-picker.tsx @@ -0,0 +1,88 @@ +import { h } from 'preact'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import { useMemo } from 'preact/hooks'; +import { useSelector, useDispatch } from 'react-redux'; + +import { StoreState } from 'store'; +import { Select } from 'components/select'; +import { updateSorting } from 'store/comments/actions'; +import type { Sorting } from 'common/types'; + +export function SortPicker() { + const dispatch = useDispatch(); + const intl = useIntl(); + const [items, itemsById] = useMemo(() => { + const sortOptions = { + '-score': intl.formatMessage(messages.best), + '+score': intl.formatMessage(messages.worst), + '-time': intl.formatMessage(messages.newest), + '+time': intl.formatMessage(messages.oldest), + '-active': intl.formatMessage(messages.recentlyUpdated), + '+active': intl.formatMessage(messages.leastRecentlyUpdated), + '-controversy': intl.formatMessage(messages.mostControversial), + '+controversy': intl.formatMessage(messages.leastControversial), + }; + type SortItem = { value: string; label: string }; + const sortItems: SortItem[] = Object.entries(sortOptions).map(([k, v]) => ({ value: k, label: v })); + const sortById = sortItems.reduce( + (accum, s) => ({ ...accum, [s.value]: s }), + {} as Record + ); + + return [sortItems, sortById]; + }, [intl]); + const sort = useSelector((s: StoreState) => s.comments.sort) || items[0].value; + const selected = itemsById[sort]; + + function handleSortChange(evt: Event) { + const { value } = evt.target as HTMLOptionElement; + + if (!(value in itemsById)) { + return; + } + + dispatch(updateSorting(value as Sorting)); + } + + return ( + + {' '} +