1
Fork 0

Rewrite PeoplePage in Typescript and add tests

This commit is contained in:
viktorstrate 2021-05-18 18:05:34 +02:00
parent 1791ef1b5c
commit cd04d34cab
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
3 changed files with 157 additions and 37 deletions

View File

@ -0,0 +1,102 @@
import '@testing-library/jest-dom'
import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import PeoplePage, { MY_FACES_QUERY } from './PeoplePage'
import { MockedProvider } from '@apollo/client/testing'
import { MemoryRouter } from 'react-router'
require('../../localization').setupLocalization()
jest.mock('../../hooks/useScrollPagination', () =>
jest.fn(() => ({
finished: true,
containerElem: jest.fn(),
}))
)
const graphqlMocks = [
{
request: {
query: MY_FACES_QUERY,
variables: {
limit: 50,
offset: 0,
},
},
result: {
data: {
myFaceGroups: [
{
__typename: 'FaceGroup',
id: '3',
label: 'Person A',
imageFaceCount: 2,
imageFaces: [
{
__typename: 'ImageFace',
id: '3',
rectangle: {
__typename: 'FaceRectangle',
minX: 0.2705079913139343,
maxX: 0.3408200144767761,
minY: 0.7691109776496887,
maxY: 0.881434977054596,
},
media: {
__typename: 'Media',
id: '63',
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_L%C3%B8berute_jpg_p9x8dLWr.jpg',
width: 1024,
height: 641,
},
},
},
],
},
{
__typename: 'FaceGroup',
id: '1',
label: 'Person B',
imageFaceCount: 1,
imageFaces: [],
},
],
},
},
},
]
test('people page', async () => {
const matchMock = {
params: {
person: undefined,
},
}
render(
<MemoryRouter initialEntries={['/people']}>
<MockedProvider mocks={graphqlMocks} addTypename={false}>
<PeoplePage match={matchMock} />
</MockedProvider>
</MemoryRouter>
)
expect(screen.getByTestId('Layout')).toBeInTheDocument()
expect(screen.getByText('Recognize unlabeled faces')).toBeInTheDocument()
await waitFor(() => {
expect(screen.getByText('Person A')).toBeInTheDocument()
expect(screen.getByText('Person B')).toBeInTheDocument()
})
expect(
screen.getAllByRole('link').some(x => x.getAttribute('href') == '/people/1')
).toBeTruthy()
expect(
screen.getAllByRole('link').some(x => x.getAttribute('href') == '/people/3')
).toBeTruthy()
})

View File

@ -1,5 +1,4 @@
import React, { createRef, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { gql, useMutation, useQuery } from '@apollo/client'
import Layout from '../../Layout'
import styled from 'styled-components'
@ -10,6 +9,16 @@ import FaceCircleImage from './FaceCircleImage'
import useScrollPagination from '../../hooks/useScrollPagination'
import PaginateLoader from '../../components/PaginateLoader'
import { useTranslation } from 'react-i18next'
import {
setGroupLabel,
setGroupLabelVariables,
} from './__generated__/setGroupLabel'
import {
myFaces,
myFacesVariables,
myFaces_myFaceGroups,
} from './__generated__/myFaces'
import { recognizeUnlabeledFaces } from './__generated__/recognizeUnlabeledFaces'
export const MY_FACES_QUERY = gql`
query myFaces($limit: Int, $offset: Int) {
@ -55,7 +64,7 @@ const RECOGNIZE_UNLABELED_FACES_MUTATION = gql`
}
`
const FaceDetailsButton = styled.button`
const FaceDetailsButton = styled.button<{ labeled: boolean }>`
color: ${({ labeled }) => (labeled ? 'black' : '#aaa')};
width: 150px;
margin: 12px auto 24px;
@ -73,13 +82,20 @@ const FaceDetailsButton = styled.button`
const FaceLabel = styled.span``
const FaceDetails = ({ group }) => {
type FaceDetailsProps = {
group: myFaces_myFaceGroups
}
const FaceDetails = ({ group }: FaceDetailsProps) => {
const { t } = useTranslation()
const [editLabel, setEditLabel] = useState(false)
const [inputValue, setInputValue] = useState(group.label ?? '')
const inputRef = createRef()
const inputRef = createRef<Input>()
const [setGroupLabel, { loading }] = useMutation(SET_GROUP_LABEL_MUTATION, {
const [setGroupLabel, { loading }] = useMutation<
setGroupLabel,
setGroupLabelVariables
>(SET_GROUP_LABEL_MUTATION, {
variables: {
groupID: group.id,
},
@ -91,9 +107,7 @@ const FaceDetails = ({ group }) => {
}
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus()
}
inputRef.current?.focus()
}, [inputRef])
useEffect(() => {
@ -102,7 +116,7 @@ const FaceDetails = ({ group }) => {
}
}, [loading])
const onKeyUp = e => {
const onKeyUp = (e: React.ChangeEvent<HTMLInputElement> & KeyboardEvent) => {
if (e.key == 'Escape') {
resetLabel()
return
@ -111,6 +125,7 @@ const FaceDetails = ({ group }) => {
if (e.key == 'Enter') {
setGroupLabel({
variables: {
groupID: group.id,
label: e.target.value == '' ? null : e.target.value,
},
})
@ -155,10 +170,6 @@ const FaceDetails = ({ group }) => {
return label
}
FaceDetails.propTypes = {
group: PropTypes.object.isRequired,
}
const FaceImagesCount = styled.span`
background-color: #eee;
color: #222;
@ -179,7 +190,11 @@ const EditIcon = styled(Icon)`
}
`
const FaceGroup = ({ group }) => {
type FaceGroupProps = {
group: myFaces_myFaceGroups
}
const FaceGroup = ({ group }: FaceGroupProps) => {
const previewFace = group.imageFaces[0]
return (
@ -192,10 +207,6 @@ const FaceGroup = ({ group }) => {
)
}
FaceGroup.propTypes = {
group: PropTypes.any,
}
const FaceGroupsWrapper = styled.div`
display: flex;
flex-wrap: wrap;
@ -204,27 +215,29 @@ const FaceGroupsWrapper = styled.div`
const PeopleGallery = () => {
const { t } = useTranslation()
const { data, error, loading, fetchMore } = useQuery(MY_FACES_QUERY, {
const { data, error, loading, fetchMore } = useQuery<
myFaces,
myFacesVariables
>(MY_FACES_QUERY, {
variables: {
limit: 50,
offset: 0,
},
})
const [
recognizeUnlabeled,
{ loading: recognizeUnlabeledLoading },
] = useMutation(RECOGNIZE_UNLABELED_FACES_MUTATION)
const [recognizeUnlabeled, { loading: recognizeUnlabeledLoading }] =
useMutation<recognizeUnlabeledFaces>(RECOGNIZE_UNLABELED_FACES_MUTATION)
const { containerElem, finished: finishedLoadingMore } = useScrollPagination({
loading,
fetchMore,
data,
getItems: data => data.myFaceGroups,
})
const { containerElem, finished: finishedLoadingMore } =
useScrollPagination<myFaces>({
loading,
fetchMore,
data,
getItems: data => data.myFaceGroups,
})
if (error) {
return error.message
return <div>{error.message}</div>
}
let faces = null
@ -258,11 +271,20 @@ const PeopleGallery = () => {
)
}
const PeoplePage = ({ match }) => {
type PeoplePageProps = {
match: {
params: {
person?: string
}
}
}
const PeoplePage = ({ match }: PeoplePageProps) => {
const { t } = useTranslation()
const faceGroup = match.params.person
if (faceGroup) {
return (
<Layout>
<Layout title={t('title.people', 'People')}>
<SingleFaceGroup faceGroupID={faceGroup} />
</Layout>
)
@ -271,8 +293,4 @@ const PeoplePage = ({ match }) => {
}
}
PeoplePage.propTypes = {
match: PropTypes.object.isRequired,
}
export default PeoplePage

View File

@ -110,7 +110,7 @@ describe('load correct share page, based on graphql query', () => {
expect(screen.getByText('Loading...')).toBeInTheDocument()
await waitForElementToBeRemoved(() => screen.getByText('Loading...'))
await waitForElementToBeRemoved(() => screen.queryByText('Loading...'))
expect(screen.getByTestId('Layout')).toBeInTheDocument()
expect(screen.getByTestId('MediaSharePage')).toBeInTheDocument()