Rewrite PeoplePage in Typescript and add tests
This commit is contained in:
parent
1791ef1b5c
commit
cd04d34cab
|
@ -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()
|
||||||
|
})
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { createRef, useEffect, useState } from 'react'
|
import React, { createRef, useEffect, useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||||
import Layout from '../../Layout'
|
import Layout from '../../Layout'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
@ -10,6 +9,16 @@ import FaceCircleImage from './FaceCircleImage'
|
||||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||||
import PaginateLoader from '../../components/PaginateLoader'
|
import PaginateLoader from '../../components/PaginateLoader'
|
||||||
import { useTranslation } from 'react-i18next'
|
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`
|
export const MY_FACES_QUERY = gql`
|
||||||
query myFaces($limit: Int, $offset: Int) {
|
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')};
|
color: ${({ labeled }) => (labeled ? 'black' : '#aaa')};
|
||||||
width: 150px;
|
width: 150px;
|
||||||
margin: 12px auto 24px;
|
margin: 12px auto 24px;
|
||||||
|
@ -73,13 +82,20 @@ const FaceDetailsButton = styled.button`
|
||||||
|
|
||||||
const FaceLabel = styled.span``
|
const FaceLabel = styled.span``
|
||||||
|
|
||||||
const FaceDetails = ({ group }) => {
|
type FaceDetailsProps = {
|
||||||
|
group: myFaces_myFaceGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
const FaceDetails = ({ group }: FaceDetailsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [editLabel, setEditLabel] = useState(false)
|
const [editLabel, setEditLabel] = useState(false)
|
||||||
const [inputValue, setInputValue] = useState(group.label ?? '')
|
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: {
|
variables: {
|
||||||
groupID: group.id,
|
groupID: group.id,
|
||||||
},
|
},
|
||||||
|
@ -91,9 +107,7 @@ const FaceDetails = ({ group }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputRef.current) {
|
inputRef.current?.focus()
|
||||||
inputRef.current.focus()
|
|
||||||
}
|
|
||||||
}, [inputRef])
|
}, [inputRef])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -102,7 +116,7 @@ const FaceDetails = ({ group }) => {
|
||||||
}
|
}
|
||||||
}, [loading])
|
}, [loading])
|
||||||
|
|
||||||
const onKeyUp = e => {
|
const onKeyUp = (e: React.ChangeEvent<HTMLInputElement> & KeyboardEvent) => {
|
||||||
if (e.key == 'Escape') {
|
if (e.key == 'Escape') {
|
||||||
resetLabel()
|
resetLabel()
|
||||||
return
|
return
|
||||||
|
@ -111,6 +125,7 @@ const FaceDetails = ({ group }) => {
|
||||||
if (e.key == 'Enter') {
|
if (e.key == 'Enter') {
|
||||||
setGroupLabel({
|
setGroupLabel({
|
||||||
variables: {
|
variables: {
|
||||||
|
groupID: group.id,
|
||||||
label: e.target.value == '' ? null : e.target.value,
|
label: e.target.value == '' ? null : e.target.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -155,10 +170,6 @@ const FaceDetails = ({ group }) => {
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
|
|
||||||
FaceDetails.propTypes = {
|
|
||||||
group: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
const FaceImagesCount = styled.span`
|
const FaceImagesCount = styled.span`
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
color: #222;
|
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]
|
const previewFace = group.imageFaces[0]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -192,10 +207,6 @@ const FaceGroup = ({ group }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
FaceGroup.propTypes = {
|
|
||||||
group: PropTypes.any,
|
|
||||||
}
|
|
||||||
|
|
||||||
const FaceGroupsWrapper = styled.div`
|
const FaceGroupsWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -204,19 +215,21 @@ const FaceGroupsWrapper = styled.div`
|
||||||
|
|
||||||
const PeopleGallery = () => {
|
const PeopleGallery = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data, error, loading, fetchMore } = useQuery(MY_FACES_QUERY, {
|
const { data, error, loading, fetchMore } = useQuery<
|
||||||
|
myFaces,
|
||||||
|
myFacesVariables
|
||||||
|
>(MY_FACES_QUERY, {
|
||||||
variables: {
|
variables: {
|
||||||
limit: 50,
|
limit: 50,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const [
|
const [recognizeUnlabeled, { loading: recognizeUnlabeledLoading }] =
|
||||||
recognizeUnlabeled,
|
useMutation<recognizeUnlabeledFaces>(RECOGNIZE_UNLABELED_FACES_MUTATION)
|
||||||
{ loading: recognizeUnlabeledLoading },
|
|
||||||
] = useMutation(RECOGNIZE_UNLABELED_FACES_MUTATION)
|
|
||||||
|
|
||||||
const { containerElem, finished: finishedLoadingMore } = useScrollPagination({
|
const { containerElem, finished: finishedLoadingMore } =
|
||||||
|
useScrollPagination<myFaces>({
|
||||||
loading,
|
loading,
|
||||||
fetchMore,
|
fetchMore,
|
||||||
data,
|
data,
|
||||||
|
@ -224,7 +237,7 @@ const PeopleGallery = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return error.message
|
return <div>{error.message}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
let faces = null
|
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
|
const faceGroup = match.params.person
|
||||||
if (faceGroup) {
|
if (faceGroup) {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout title={t('title.people', 'People')}>
|
||||||
<SingleFaceGroup faceGroupID={faceGroup} />
|
<SingleFaceGroup faceGroupID={faceGroup} />
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
@ -271,8 +293,4 @@ const PeoplePage = ({ match }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PeoplePage.propTypes = {
|
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PeoplePage
|
export default PeoplePage
|
|
@ -110,7 +110,7 @@ describe('load correct share page, based on graphql query', () => {
|
||||||
|
|
||||||
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByText('Loading...'))
|
await waitForElementToBeRemoved(() => screen.queryByText('Loading...'))
|
||||||
|
|
||||||
expect(screen.getByTestId('Layout')).toBeInTheDocument()
|
expect(screen.getByTestId('Layout')).toBeInTheDocument()
|
||||||
expect(screen.getByTestId('MediaSharePage')).toBeInTheDocument()
|
expect(screen.getByTestId('MediaSharePage')).toBeInTheDocument()
|
||||||
|
|
Loading…
Reference in New Issue