-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from strawberrycheeks/module9-task2
- Loading branch information
Showing
17 changed files
with
364 additions
and
79 deletions.
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
This file was deleted.
Oops, something went wrong.
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 @@ | ||
export { CityPlaces } from './ui/city-places'; |
File renamed without changes.
File renamed without changes.
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,177 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { Provider } from 'react-redux'; | ||
import { BrowserRouter } from 'react-router-dom'; | ||
|
||
import { initAsyncActionsStore } from '@/app/lib/mocks'; | ||
import { cities } from '@/entities/city'; | ||
import { makeOffers } from '@/entities/offer-card/lib/mocks'; | ||
import { AuthorizationStatus, NameSpace } from '@/shared/model/enums'; | ||
|
||
import { SortVariant } from '../model/types'; | ||
import { CityPlaces } from './city-places'; | ||
|
||
describe('<CityPlaces />', () => { | ||
const { mockStoreCreator } = initAsyncActionsStore(); | ||
let store: ReturnType<typeof mockStoreCreator>; | ||
|
||
const offers = makeOffers(); | ||
|
||
beforeEach(() => { | ||
store = mockStoreCreator({ | ||
[NameSpace.USER]: { | ||
authorizationStatus: AuthorizationStatus.AUTH, | ||
}, | ||
[NameSpace.OFFER]: { | ||
activeOfferId: undefined, | ||
}, | ||
}); | ||
}); | ||
|
||
it('should render correctly', () => { | ||
const component = ( | ||
<Provider store={store}> | ||
<BrowserRouter> | ||
<CityPlaces city={cities.Paris} offers={offers} /> | ||
</BrowserRouter> | ||
</Provider> | ||
); | ||
|
||
const { container } = render(component); | ||
|
||
expect(container.querySelector('.cities')).toBeInTheDocument(); | ||
expect(container.querySelector('.cities__places')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render empty state if offers is empty', () => { | ||
const component = ( | ||
<Provider store={store}> | ||
<BrowserRouter> | ||
<CityPlaces city={cities.Paris} offers={[]} /> | ||
</BrowserRouter> | ||
</Provider> | ||
); | ||
|
||
render(component); | ||
|
||
expect(screen.getByText('No places to stay available')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render first offer of array if default order', () => { | ||
const component = ( | ||
<Provider store={store}> | ||
<BrowserRouter> | ||
<CityPlaces city={cities.Paris} offers={offers} /> | ||
</BrowserRouter> | ||
</Provider> | ||
); | ||
|
||
const { container } = render(component); | ||
|
||
const firstOfferId: string = | ||
container.querySelector<HTMLElement>('.place-card[data-offer-id]') | ||
?.dataset.offerId ?? '-1'; | ||
const targetOfferId = offers[0]?.id ?? -1; | ||
|
||
expect(firstOfferId).toBe(targetOfferId); | ||
}); | ||
|
||
it('should render most rated offer of array if top rated sorting', async () => { | ||
const component = ( | ||
<Provider store={store}> | ||
<BrowserRouter> | ||
<CityPlaces city={cities.Paris} offers={offers} /> | ||
</BrowserRouter> | ||
</Provider> | ||
); | ||
|
||
const { container } = render(component); | ||
|
||
await userEvent.click(container.querySelector('.places__sorting-type')!); | ||
|
||
const sortingOptions = container.querySelectorAll('.places__option'); | ||
let sortingOptionIndex = -1; | ||
sortingOptions.forEach((option, index) => { | ||
if (option.textContent === SortVariant.TOP_RATED) { | ||
sortingOptionIndex = index; | ||
} | ||
}); | ||
await userEvent.click( | ||
container.querySelectorAll('.places__option')[sortingOptionIndex]!, | ||
); | ||
|
||
const firstOfferId: string = | ||
container.querySelector<HTMLElement>('.place-card[data-offer-id]') | ||
?.dataset.offerId ?? '-1'; | ||
const targetOfferId = | ||
offers.toSorted((a, b) => b.rating - a.rating)[0]?.id ?? -1; | ||
|
||
expect(firstOfferId).toBe(targetOfferId); | ||
}); | ||
|
||
it('should render most expensive offer of array if most expensive sorting', async () => { | ||
const component = ( | ||
<Provider store={store}> | ||
<BrowserRouter> | ||
<CityPlaces city={cities.Paris} offers={offers} /> | ||
</BrowserRouter> | ||
</Provider> | ||
); | ||
|
||
const { container } = render(component); | ||
|
||
await userEvent.click(container.querySelector('.places__sorting-type')!); | ||
|
||
const sortingOptions = container.querySelectorAll('.places__option'); | ||
let sortingOptionIndex = -1; | ||
sortingOptions.forEach((option, index) => { | ||
if (option.textContent === SortVariant.HIGH_TO_LOW) { | ||
sortingOptionIndex = index; | ||
} | ||
}); | ||
await userEvent.click( | ||
container.querySelectorAll('.places__option')[sortingOptionIndex]!, | ||
); | ||
|
||
const firstOfferId: string = | ||
container.querySelector<HTMLElement>('.place-card[data-offer-id]') | ||
?.dataset.offerId ?? '-1'; | ||
const targetOfferId = | ||
offers.toSorted((a, b) => b.price - a.price)[0]?.id ?? -1; | ||
|
||
expect(firstOfferId).toBe(targetOfferId); | ||
}); | ||
|
||
it('should render most cheap offer of array if most cheap sorting', async () => { | ||
const component = ( | ||
<Provider store={store}> | ||
<BrowserRouter> | ||
<CityPlaces city={cities.Paris} offers={offers} /> | ||
</BrowserRouter> | ||
</Provider> | ||
); | ||
|
||
const { container } = render(component); | ||
|
||
await userEvent.click(container.querySelector('.places__sorting-type')!); | ||
|
||
const sortingOptions = container.querySelectorAll('.places__option'); | ||
let sortingOptionIndex = -1; | ||
sortingOptions.forEach((option, index) => { | ||
if (option.textContent === SortVariant.LOW_TO_HIGH) { | ||
sortingOptionIndex = index; | ||
} | ||
}); | ||
await userEvent.click( | ||
container.querySelectorAll('.places__option')[sortingOptionIndex]!, | ||
); | ||
|
||
const firstOfferId: string = | ||
container.querySelector<HTMLElement>('.place-card[data-offer-id]') | ||
?.dataset.offerId ?? '-1'; | ||
const targetOfferId = | ||
offers.toSorted((a, b) => a.price - b.price)[0]?.id ?? -1; | ||
|
||
expect(firstOfferId).toBe(targetOfferId); | ||
}); | ||
}); |
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 @@ | ||
export { CityPlaces } from './city-places'; |
File renamed without changes.
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,63 +1 @@ | ||
import { useState } from 'react'; | ||
|
||
import { ReviewRating } from './ReviewRating'; | ||
|
||
type ReviewFormProps = { | ||
onSubmit: ({ rating, comment }: { rating: number; comment: string }) => void; | ||
}; | ||
|
||
export const ReviewForm = (props: ReviewFormProps) => { | ||
const { onSubmit: submit } = props; | ||
|
||
const [rating, setRating] = useState(0); | ||
const [comment, setComment] = useState(''); | ||
|
||
const isSubmitDisabled = | ||
rating === 0 || comment.length < 50 || comment.length > 300; | ||
|
||
const handleTextChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
setComment(event.target.value); | ||
}; | ||
|
||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { | ||
event.preventDefault(); | ||
submit({ rating, comment }); | ||
|
||
setRating(0); | ||
setComment(''); | ||
}; | ||
|
||
return ( | ||
<form className="reviews__form form" onSubmit={handleSubmit}> | ||
<label className="reviews__label form__label" htmlFor="review"> | ||
Your review | ||
</label> | ||
|
||
<ReviewRating value={rating} onChange={setRating} /> | ||
|
||
<textarea | ||
className="reviews__textarea form__textarea" | ||
id="review" | ||
name="review" | ||
placeholder="Tell how was your stay, what you like and what can be improved" | ||
value={comment} | ||
onChange={handleTextChange} | ||
/> | ||
|
||
<div className="reviews__button-wrapper"> | ||
<p className="reviews__help"> | ||
To submit review please make sure to set{' '} | ||
<span className="reviews__star">rating</span> and describe your stay | ||
with at least <b className="reviews__text-amount">50 characters</b>. | ||
</p> | ||
<button | ||
className="reviews__submit form__submit button" | ||
type="submit" | ||
disabled={isSubmitDisabled} | ||
> | ||
Submit | ||
</button> | ||
</div> | ||
</form> | ||
); | ||
}; | ||
export { ReviewForm } from './review-form'; |
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,58 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
import { ReviewForm } from './review-form'; | ||
|
||
describe('<ReviewForm />', () => { | ||
it('should render correctly', () => { | ||
const onSubmit = vitest.fn(); | ||
|
||
const component = <ReviewForm onSubmit={onSubmit} />; | ||
|
||
const { container } = render(component); | ||
|
||
expect(screen.getByText('Your review')).toBeInTheDocument(); | ||
expect(container.querySelector('.reviews__form')).toBeInTheDocument(); | ||
}); | ||
|
||
it('button should be disabled by default', () => { | ||
const onSubmit = vitest.fn(); | ||
|
||
const component = <ReviewForm onSubmit={onSubmit} />; | ||
|
||
const { container } = render(component); | ||
|
||
expect( | ||
container.querySelector<HTMLButtonElement>('button[type="submit"]') | ||
?.disabled, | ||
).toBe(true); | ||
}); | ||
|
||
it('should call onSubmit with correct data', async () => { | ||
const onSubmit = vitest.fn(); | ||
|
||
const component = <ReviewForm onSubmit={onSubmit} />; | ||
|
||
const { container } = render(component); | ||
|
||
await userEvent.click( | ||
container.querySelectorAll<HTMLInputElement>('.form__rating-input')[1]!, | ||
); | ||
await userEvent.click(container.querySelector('textarea')!); | ||
const textToPaste = Array.from({ length: 51 }) | ||
.map(() => 'a') | ||
.join(''); | ||
await userEvent.paste(textToPaste); | ||
|
||
expect( | ||
container.querySelector<HTMLButtonElement>('button[type="submit"]') | ||
?.disabled, | ||
).toBe(false); | ||
|
||
await userEvent.click( | ||
container.querySelector<HTMLButtonElement>('button[type="submit"]')!, | ||
); | ||
|
||
expect(onSubmit).toBeCalledWith({ rating: 4, comment: textToPaste }); | ||
}); | ||
}); |
Oops, something went wrong.