-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1c23184
commit 2a5a752
Showing
8 changed files
with
217 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useQuery } from '@tanstack/react-query' | ||
import { useAuthedFetch } from './useAuthedFetch' | ||
|
||
export interface UsersIndex { | ||
id: string | ||
email: string | ||
role: 'member' | 'admin' | ||
} | ||
|
||
export const QUERY_KEY = 'useUsers' | ||
|
||
export const useUsers = () => { | ||
const authedFetch = useAuthedFetch() | ||
|
||
return useQuery({ | ||
queryKey: [QUERY_KEY], | ||
queryFn: async () => { | ||
const result = await authedFetch<{ users: UsersIndex[] }>({ | ||
path: '/users', | ||
method: 'GET', | ||
}) | ||
return result | ||
.json!.users.map((user) => ({ ...user, role: user.role ?? 'member' })) | ||
.sort((a, b) => a.email.localeCompare(b.email)) | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import React from 'react' | ||
import { render } from '@testing-library/react' | ||
import capitalize from 'lodash/capitalize' | ||
import UsersPage from '../users' | ||
import { SIDEBAR_NAVIGATION_TEST_ID as sidebarNavigationTestId } from 'src/ui/SidebarNavigation' | ||
import { asMock, buildUserIndex, buildUseQueryResult, urlFor } from 'src/testHelpers' | ||
import { useUsers } from 'src/network/useUsers' | ||
import { UsersIndex } from 'src/network/useUsers' | ||
import { faker } from '@faker-js/faker' | ||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' | ||
|
||
jest.mock('src/network/useUsers', () => { | ||
return { | ||
useUsers: jest.fn(), | ||
} | ||
}) | ||
|
||
describe('Users page', () => { | ||
const renderUsersPage = () => { | ||
return render( | ||
<QueryClientProvider client={new QueryClient()}> | ||
<UsersPage /> | ||
</QueryClientProvider>, | ||
) | ||
} | ||
|
||
it('is displayed in a layout', () => { | ||
const query = buildUseQueryResult<UsersIndex[]>({ isLoading: true, data: undefined }) | ||
asMock(useUsers).mockReturnValue(query) | ||
const { baseElement } = renderUsersPage() | ||
expect(baseElement.querySelector('.layout')).not.toBeNull() | ||
}) | ||
|
||
it('displays the sidebar navigation', () => { | ||
const query = buildUseQueryResult<UsersIndex[]>({ isLoading: true, data: undefined }) | ||
asMock(useUsers).mockReturnValue(query) | ||
const { queryByTestId } = renderUsersPage() | ||
expect(queryByTestId(sidebarNavigationTestId)).not.toBeNull() | ||
}) | ||
|
||
describe('when loading', () => { | ||
it('displays an loading spinner', () => { | ||
const query = buildUseQueryResult<UsersIndex[]>({ isLoading: true, data: undefined }) | ||
asMock(useUsers).mockReturnValue(query) | ||
const { queryByText } = renderUsersPage() | ||
expect(queryByText('Loading the users')).not.toBeNull() | ||
}) | ||
}) | ||
|
||
describe('when successful', () => { | ||
it('displays the users', () => { | ||
const users = [buildUserIndex({ role: 'admin' }), buildUserIndex()] | ||
const [user1, user2] = users | ||
const query = buildUseQueryResult({ data: users }) | ||
asMock(useUsers).mockReturnValue(query) | ||
|
||
const { queryByText } = renderUsersPage() | ||
|
||
const firstEmail = queryByText(user1.email) | ||
expect(firstEmail).not.toBeNull() | ||
|
||
const firstRole = queryByText(capitalize(user1.role)) | ||
expect(firstRole).not.toBeNull() | ||
|
||
const secondEmail = queryByText(user2.email) | ||
expect(secondEmail).not.toBeNull() | ||
|
||
const secondRole = queryByText(capitalize(user2.role)) | ||
expect(secondRole).not.toBeNull() | ||
}) | ||
}) | ||
|
||
describe('when there is an error', () => { | ||
it('displays an error', () => { | ||
const error = new Error(faker.lorem.sentence()) | ||
const query = buildUseQueryResult<UsersIndex[]>({ error, isError: true }) | ||
asMock(useUsers).mockReturnValue(query) | ||
const { queryByText } = renderUsersPage() | ||
expect(queryByText(error.message)).not.toBeNull() | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.user-list { | ||
margin-top: 3rem; | ||
margin-bottom: 3rem; | ||
} | ||
|
||
.user-item { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.5rem; | ||
border-bottom: 1px solid var(--gray-light); | ||
padding: 1.5rem 0; | ||
} | ||
|
||
.user-email { | ||
font-size: 1.25rem; | ||
font-weight: bold; | ||
} | ||
|
||
.user-role { | ||
color: var(--gray-dark); | ||
font-style: italic; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import React, { FC } from 'react' | ||
import capitalize from 'lodash.capitalize' | ||
import { HeadFC } from 'gatsby' | ||
import { | ||
Heading, | ||
Layout, | ||
List, | ||
PageContent, | ||
Paragraph, | ||
Sidebar, | ||
SkipNavContent, | ||
SpacedContainer, | ||
SidebarNavigation, | ||
LoadingOverlay, | ||
} from 'src/ui' | ||
import { formatPageTitle } from 'src/utils/formatPageTitle' | ||
import { useUsers } from 'src/network/useUsers' | ||
import './users.css' | ||
|
||
const UsersPage: FC = () => { | ||
const { data: users, isLoading, error } = useUsers() | ||
|
||
return ( | ||
<Layout element="div"> | ||
<Sidebar> | ||
<SidebarNavigation /> | ||
</Sidebar> | ||
<PageContent element="main"> | ||
<SkipNavContent /> | ||
<SpacedContainer> | ||
<Heading element="h1">Users</Heading> | ||
<Paragraph>All of users can be found here</Paragraph> | ||
{error && <Paragraph>{error.message}</Paragraph>} | ||
{users && users.length > 0 && ( | ||
<List className="user-list"> | ||
{users.map((user) => ( | ||
<li key={user.id} className="user-item"> | ||
<span className="user-email">{user.email}</span> | ||
<span className="user-role">{capitalize(user.role)}</span> | ||
</li> | ||
))} | ||
</List> | ||
)} | ||
|
||
{isLoading && <LoadingOverlay description="Loading the users" />} | ||
</SpacedContainer> | ||
</PageContent> | ||
</Layout> | ||
) | ||
} | ||
|
||
export default UsersPage | ||
|
||
export const Head: HeadFC = () => <title>{formatPageTitle('Users')}</title> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
export const currentUrlSearchParamsFor = (key: string) => { | ||
return typeof window !== 'undefined' ? new URLSearchParams(window.location.search).get(key) : null | ||
} | ||
} |