Update UI for user comments sidebar

This commit is contained in:
esvyridov
2021-09-04 12:45:07 -05:00
committed by Umputun
parent bb3c86281c
commit 3d3162de41
19 changed files with 91 additions and 23 deletions
@@ -67,6 +67,11 @@
padding: 0 16px 16px;
}
.error {
font-size: 14px;
margin: 0 0 4px;
}
.content::-webkit-scrollbar {
width: 10px;
}
@@ -101,6 +106,7 @@
.info {
max-width: 100%;
margin: 0;
padding-right: 8px;
overflow: hidden;
line-height: 1;
}
@@ -109,6 +115,7 @@
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
font-weight: 700;
}
@@ -117,6 +124,7 @@
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
color: var(--color13);
}
@@ -160,7 +168,7 @@
}
.preloader {
margin: 0 auto 18px;
margin: 0 auto;
color: var(--color13);
}
@@ -1,6 +1,5 @@
import { h } from 'preact';
import '@testing-library/jest-dom';
import { waitFor } from '@testing-library/preact';
import { render } from 'tests/utils';
import * as api from 'common/api';
@@ -43,48 +42,68 @@ const commentsStub = [commentStub, commentStub, commentStub];
describe('<Profile />', () => {
it('should render preloader', () => {
jest.spyOn(pq, 'parseQuery').mockImplementation(() => ({ ...userParamsStub }));
const { container } = render(<Profile />);
const { queryByLabelText, queryByRole } = render(<Profile />);
expect(container.querySelector('[aria-label="Loading..."]')).toBeInTheDocument();
expect(queryByLabelText('Loading...')).toBeInTheDocument();
expect(queryByRole('button', { name: /retry/i })).not.toBeInTheDocument();
expect(queryByRole('heading', { name: /recent comments/i })).not.toBeInTheDocument();
});
it('should render error', async () => {
jest.spyOn(pq, 'parseQuery').mockImplementation(() => ({ ...userParamsStub }));
jest.spyOn(api, 'getUserComments').mockImplementation(async () => {
throw new Error('error');
});
const { queryByLabelText, queryByRole, findByRole } = render(<Profile />);
expect(await findByRole('button', { name: /retry/i })).toBeInTheDocument();
expect(queryByLabelText('Loading...')).not.toBeInTheDocument();
expect(queryByRole('heading', { name: /recent comments/i })).not.toBeInTheDocument();
});
it('should render without comments', async () => {
jest.spyOn(pq, 'parseQuery').mockImplementation(() => ({ ...userParamsStub }));
const getUserComments = jest.spyOn(api, 'getUserComments').mockImplementation(async () => ({ comments: [] }));
const { getByText } = render(<Profile />);
const { findByText, queryByLabelText, queryByRole } = render(<Profile />);
await waitFor(() => expect(getUserComments).toHaveBeenCalledWith('1'));
await waitFor(() => expect(getByText("Don't have comments yet")).toBeInTheDocument());
expect(getUserComments).toHaveBeenCalledWith('1');
expect(await findByText("Don't have comments yet")).toBeInTheDocument();
expect(queryByLabelText('Loading...')).not.toBeInTheDocument();
expect(queryByRole('button', { name: /retry/i })).not.toBeInTheDocument();
});
it('should render user with comments', async () => {
jest.spyOn(pq, 'parseQuery').mockImplementation(() => userParamsStub);
jest.spyOn(api, 'getUserComments').mockImplementation(async () => ({ comments: commentsStub }));
const { getByText } = render(<Profile />);
const { findByText, queryByLabelText, queryByRole } = render(<Profile />);
await waitFor(() => expect(getByText('Recent comments')).toBeInTheDocument());
expect(await findByText('Recent comments')).toBeInTheDocument();
expect(queryByLabelText('Loading...')).not.toBeInTheDocument();
expect(queryByRole('button', { name: /retry/i })).not.toBeInTheDocument();
});
it('shoud render current user without comments', async () => {
it('should render current user without comments', async () => {
jest.spyOn(pq, 'parseQuery').mockImplementation(() => ({ ...userParamsStub, current: '1' }));
jest.spyOn(api, 'getUserComments').mockImplementation(async () => ({ comments: [] }));
const { getByText, getByTitle } = render(<Profile />);
const { getByText, getByTitle, findByText } = render(<Profile />);
expect(getByTitle('Sign Out')).toBeInTheDocument();
expect(getByText('Request my data removal')).toBeInTheDocument();
expect(await findByText("Don't have comments yet")).toBeInTheDocument();
});
it('shoud render current user with comments', async () => {
it('should render current user with comments', async () => {
jest.spyOn(pq, 'parseQuery').mockImplementation(() => ({ ...userParamsStub, current: '1' }));
jest.spyOn(api, 'getUserComments').mockImplementation(async () => ({ comments: commentsStub }));
const { getByText, getByTitle } = render(<Profile />);
const { findByText, queryByTitle, queryByText } = render(<Profile />);
expect(getByTitle('Sign Out')).toBeInTheDocument();
expect(getByText('Request my data removal')).toBeInTheDocument();
await waitFor(() => expect(getByText('My recent comments')).toBeInTheDocument());
expect(queryByTitle('Sign Out')).toBeInTheDocument();
expect(queryByText('Request my data removal')).toBeInTheDocument();
expect(await findByText('My recent comments')).toBeInTheDocument();
});
it('should render user without footer', async () => {
+32 -7
View File
@@ -32,10 +32,27 @@ export function Profile() {
const intl = useIntl();
const rootRef = useRef<HTMLDivElement>(null);
const user = useMemo(() => parseQuery(), []);
const [isCommentsLoading, setIsCommentsLoading] = useState(false);
const [error, setError] = useState(false);
const [comments, setComments] = useState<CommentType[] | null>(null);
const [isSigningOut, setSigningOut] = useState(false);
async function fetchUserComments(userId: string) {
setIsCommentsLoading(true);
setError(false);
setComments(null);
try {
const { comments } = await getUserComments(userId);
setComments(comments);
} catch (err) {
setError(true);
} finally {
setIsCommentsLoading(false);
}
}
function handleClickClose() {
const rootElement = rootRef.current;
@@ -58,10 +75,12 @@ export function Profile() {
await signout();
}
function handleClickRetryCommentsRequest() {
fetchUserComments(user.id);
}
useEffect(() => {
getUserComments(user.id)
.then(({ comments }) => setComments(comments))
.catch(() => setError(true));
fetchUserComments(user.id);
}, [user.id]);
useEffect(() => {
@@ -154,11 +173,17 @@ export function Profile() {
</header>
<section className={clsx('profile-content', styles.content)}>
{error && (
<p className={clsx('profile-error', styles.error)}>
<FormattedMessage id="errors.0" defaultMessage="Something went wrong. Please try again a bit later." />
</p>
<>
<p className={clsx('profile-error', styles.error)}>
<FormattedMessage id="errors.0" defaultMessage="Something went wrong. Please try again a bit later." />
</p>
<Button kind="link" size="sm" onClick={handleClickRetryCommentsRequest}>
<FormattedMessage id="retry" defaultMessage="Retry" />
</Button>
</>
)}
{comments === null ? <Preloader /> : commentsJSX}
{isCommentsLoading && <Preloader className={styles.preloader} />}
{comments !== null && commentsJSX}
</section>
{isCurrent ? (
<footer className={clsx('profile-footer', styles.footer)}>
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Нешта пайшло не так.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Замацаваныя каментары",
"root.powered-by": "Powered by <a>Remark42</a>",
"root.show-more": "Паказаць яшчэ",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Има някакъв проблем.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Фиксирани коментари",
"root.powered-by": "Използва <a>Remark42</a>",
"root.show-more": "Покажи още",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Algo deu errado.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Comentários afixados",
"root.powered-by": "Com a tecnologia <a>Remark42</a>",
"root.show-more": "Mostrar mais",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Leider ist etwas schiefgegangen.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Angeheftete Kommentare",
"root.powered-by": "Unterstützt von <a>Remark42</a>",
"root.show-more": "Mehr anzeigen",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Something went wrong.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Pinned comments",
"root.powered-by": "Powered by <a>Remark42</a>",
"root.show-more": "Show more",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Algo salió mal.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Comentarios anclados",
"root.powered-by": "Con la tecnología de <a>Remark42</a>",
"root.show-more": "Mostrar más",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Jotain meni pieleen.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Kiinnitetyt kommentit",
"root.powered-by": "Powered by <a>Remark42</a>",
"root.show-more": "Näytä lisää",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Un problème est survenu.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Commentaires épinglés",
"root.powered-by": "Powered by <a>Remark42</a>",
"root.show-more": "Afficher plus",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "不明なエラーが発生しました.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "ピン留めしたコメント",
"root.powered-by": "提供元: <a>Remark42</a>",
"root.show-more": "その他を表示",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "문제가 발생했습니다.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "고정된 댓글",
"root.powered-by": "<a>Remark42</a> 제공",
"root.show-more": "더보기",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Coś poszło nie tak.",
"profile.close": "Zamknij profil",
"profile.request-to-delete-data": "Poproś o usunięcie moich danych",
"retry": "Retry",
"root.pinned-comments": "Przypięte komentarze",
"root.powered-by": "Obsługiwane przez <a>Remark42</a>",
"root.show-more": "Pokaż więcej",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Что-то пошло не так.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Повторить",
"root.pinned-comments": "Закрепленные комментарии",
"root.powered-by": "Работает на базе <a>Remark42</a>",
"root.show-more": "Показать еще",
+1
View File
@@ -106,6 +106,7 @@
"errors.not-authorized": "Yetki yetersiz.",
"errors.to-many-request": "Maksimum talep limitine ulaştınız.",
"errors.unexpected-error": "Bir hata oluştu.",
"retry": "Retry",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"root.pinned-comments": "Sabitlenmiş yorumlar",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Щось пішло не так.",
"profile.close": "Закрити профіль",
"profile.request-to-delete-data": "Зробити запит на знищення моїх даних",
"retry": "Retry",
"root.pinned-comments": "Закріпленні коментарі",
"root.powered-by": "Powered by <a>Remark42</a>",
"root.show-more": "Показати ще",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "Có gì đó sai sai.",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "Bình buận đã ghim",
"root.powered-by": "Powered by <a>Remark42</a>",
"root.show-more": "Xem thêm",
+1
View File
@@ -108,6 +108,7 @@
"errors.unexpected-error": "出了些问题。",
"profile.close": "Close profile",
"profile.request-to-delete-data": "Request my data removal",
"retry": "Retry",
"root.pinned-comments": "固定的评论",
"root.powered-by": "由 <a>Remark42</a> 提供支持",
"root.show-more": "显示更多",