diff --git a/src/App.js b/src/App.js index 77dddb4..46930bd 100644 --- a/src/App.js +++ b/src/App.js @@ -8,7 +8,7 @@ import Header from './components/header/Header'; import VideoPage from './components/content/VideoPage/VideoPage'; import SearchResult from './components/content/SearchResult'; import NotFound from './components/content/NotFound'; -import InitialPage from './components/content/InitialPage'; +import Profile from './components/Profile'; class App extends Component { render() { @@ -28,6 +28,7 @@ class App extends Component { path="/results/:searchParam" render={(props) => } /> + diff --git a/src/__tests__/AltHeader.test.js b/src/__tests__/AltHeader.test.js new file mode 100644 index 0000000..43a0715 --- /dev/null +++ b/src/__tests__/AltHeader.test.js @@ -0,0 +1,50 @@ +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; +import App from '../App'; +import mockSearchVideo from '../__mocks__/mockSearchVideo'; +import * as api from '../api/service'; + +jest.mock('react-router-dom', () => { + const moduloOriginal = jest.requireActual('react-router-dom'); + return { + ...moduloOriginal, + BrowserRouter: ({ children }) =>
{children}
, + }; +}); + +jest.mock('../api/service'); +api.searchVideos.mockImplementation(() => Promise.resolve(mockSearchVideo)); + +function renderWithRouter(ui, routeConfigs = {}) { + const route = routeConfigs.route || '/'; + const history = routeConfigs.history || createMemoryHistory({ initialEntries: [route] }); + return { + ...render({ui}), + history, + }; +} + +describe('Funcionalidades Componente Header', () => { + it('Renderiza dois links na tela', () => { + const { container } = renderWithRouter(); + const links = container.querySelectorAll('a'); + expect(links.length).toBe(3); + expect(links[1].href).toMatch('/results'); + }); + + it('Ao fazer uma busca redireciona a página de resultados', async () => { + const { getAllByRole, getByPlaceholderText, history } = renderWithRouter( + , + ); + expect(history.location.pathname).toBe('/'); + + const searchText = 'bugs'; + fireEvent.change(getByPlaceholderText(/search/i), { target: { value: searchText } }); + fireEvent.click(getAllByRole('link')[1]); + + await waitFor(() => expect(api.searchVideos).toHaveBeenCalled()); + expect(history.location.pathname).toBe(`/results/${searchText}`); + }); +}); diff --git a/src/__tests__/AltSearchResults.test.js b/src/__tests__/AltSearchResults.test.js new file mode 100644 index 0000000..c0d0bea --- /dev/null +++ b/src/__tests__/AltSearchResults.test.js @@ -0,0 +1,65 @@ +import React from 'react'; +import { + render, + screen, + fireEvent, + getByRole, + waitFor, +} from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; +import SearchResult from '../components/content/SearchResult/index'; +import App from '../App'; +import mockSearchVideo from '../__mocks__/mockSearchVideo'; +import mockGetVideoInfo from '../__mocks__/mockGetVideoInfo'; +import mockGetVideoComments from '../__mocks__/mockGetVideoComments'; +import * as api from '../api/service'; + +jest.mock('react-router-dom', () => { + const moduloOriginal = jest.requireActual('react-router-dom'); + return { + ...moduloOriginal, + BrowserRouter: ({ children }) =>
{children}
, + }; +}); + +jest.mock('../api/service'); +api.searchVideos.mockImplementation(() => Promise.resolve(mockSearchVideo)); +api.getVideoInfo.mockImplementation(() => Promise.resolve(mockGetVideoInfo)); +api.getVideoComments.mockImplementation(() => Promise.resolve(mockGetVideoComments)); +api.getRelatedVideos.mockImplementation(() => Promise.resolve(mockSearchVideo)); + +function renderWithRouter(ui, routeConfigs = {}) { + const route = routeConfigs.route || '/'; + const history = routeConfigs.history || createMemoryHistory({ initialEntries: [route] }); + return { + ...render({ui}), + history, + }; +} + +describe('Funcionalidades Componente Search Result', () => { + it('Renderiza uma lista de videos em cima da busca', async () => { + renderWithRouter( + , + ); + await waitFor(() => expect(api.searchVideos).toHaveBeenCalled()); + expect(screen.getAllByRole('link').length).toBeLessThan( + mockSearchVideo.items.length, + ); + }); + + it('Ao clicar em um video redireciona a pagina de display', async () => { + const { history } = renderWithRouter(, { route: '/results/bugs' }); + await waitFor(() => expect(api.searchVideos).toHaveBeenCalled()); + + const videoLink = screen.getAllByRole('link')[3]; + fireEvent.click(videoLink); + expect(history.location.pathname).toMatch(/watch/i); + + await waitFor(() => expect(api.getVideoInfo).toHaveBeenCalled()); + await waitFor(() => expect(api.getVideoComments).toHaveBeenCalled()); + + expect(screen.getByTestId('videoplayer')).toBeInTheDocument(); + }); +}); diff --git a/src/__tests__/AltVideoPage.test.js b/src/__tests__/AltVideoPage.test.js new file mode 100644 index 0000000..e2a6a7b --- /dev/null +++ b/src/__tests__/AltVideoPage.test.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; +import VideoPage from '../components/content/VideoPage/VideoPage'; +import mockSearchVideo from '../__mocks__/mockSearchVideo'; +import mockGetVideoInfo from '../__mocks__/mockGetVideoInfo'; +import mockGetVideoComments from '../__mocks__/mockGetVideoComments'; +import * as api from '../api/service'; + +jest.mock('react-router-dom', () => { + const moduloOriginal = jest.requireActual('react-router-dom'); + return { + ...moduloOriginal, + BrowserRouter: ({ children }) =>
{children}
, + useHistory: () => ({ push: jest.fn() }), + }; +}); + +jest.mock('../api/service'); +api.getVideoInfo.mockImplementation(() => Promise.resolve(mockGetVideoInfo)); +api.getVideoComments.mockImplementation(() => Promise.resolve(mockGetVideoComments)); +api.getRelatedVideos.mockImplementation(() => Promise.resolve(mockSearchVideo)); + +function renderWithRouter(ui, routeConfigs = {}) { + const route = routeConfigs.route || '/'; + const history = routeConfigs.history || createMemoryHistory({ initialEntries: [route] }); + return { + ...render({ui}), + history, + }; +} + +describe('Funcionalidades Componente Video Page', () => { + it('Renderiza dados no vídeo na página', async () => { + const randomVideoID = mockSearchVideo.items[1].id.videoId; + + renderWithRouter( + , + ); + + await waitFor(() => expect(api.getVideoInfo).toHaveBeenCalled()); + await waitFor(() => expect(api.getVideoComments).toHaveBeenCalled()); + + expect(screen.getByTestId('videoplayer')).toBeInTheDocument(); + expect(screen.getByTestId('videoinfo')).toBeInTheDocument(); + expect(screen.getByTestId('channelinfo')).toBeInTheDocument(); + expect(screen.getByTestId('comments')).toBeInTheDocument(); + }); + + it('Vídeo selecionado atualiza os dados do vídeo atual na página', async () => { + const randomVideoID = mockSearchVideo.items[1].id.videoId; + const { history } = renderWithRouter( + , + { route: `/watch/${randomVideoID}` }, + ); + + await waitFor(() => expect(api.getVideoInfo).toHaveBeenCalled()); + await waitFor(() => expect(api.getVideoComments).toHaveBeenCalled()); + await waitFor(() => expect(api.getRelatedVideos).toHaveBeenCalled()); + expect(history.location.pathname).toBe(`/watch/${randomVideoID}`); + + fireEvent.click(screen.getAllByTestId('selectedVideo')[2]); + await waitFor(() => expect(api.getVideoInfo).toHaveBeenCalled()); + await waitFor(() => expect(api.getVideoComments).toHaveBeenCalled()); + await waitFor(() => expect(api.getRelatedVideos).toHaveBeenCalled()); + + expect(history.location.pathname).not.toEqual(`/watch/${randomVideoID}`); + }); +}); diff --git a/src/__tests__/SearchResult.test.js b/src/__tests__/SearchResult.test.js index 3ee1ff0..07898d2 100644 --- a/src/__tests__/SearchResult.test.js +++ b/src/__tests__/SearchResult.test.js @@ -7,7 +7,7 @@ import App from '../App'; import mockSearchVideo from '../__mocks__/mockSearchVideo'; import mockGetVideoInfo from '../__mocks__/mockGetVideoInfo'; import mockGetVideoComments from '../__mocks__/mockGetVideoComments'; -import * as api from '../api/service' +import * as api from '../api/service'; jest.mock('react-router-dom', () => { const moduloOriginal = jest.requireActual('react-router-dom'); @@ -15,17 +15,17 @@ jest.mock('react-router-dom', () => { ...moduloOriginal, BrowserRouter: ({ children }) => (
{children}
), }; -}) +}); jest.mock('../api/service'); api.searchVideos.mockImplementation( - () => Promise.resolve(mockSearchVideo) + () => Promise.resolve(mockSearchVideo), ); api.getVideoInfo.mockImplementation( - () => Promise.resolve(mockGetVideoInfo) + () => Promise.resolve(mockGetVideoInfo), ); api.getVideoComments.mockImplementation( - () => Promise.resolve(mockGetVideoComments) + () => Promise.resolve(mockGetVideoComments), ); function renderWithRouter(ui, routeConfigs = {}) { @@ -42,7 +42,7 @@ describe('Funcionalidades Componente Search Result', () => { renderWithRouter(); await waitFor(() => expect(api.searchVideos).toHaveBeenCalled()); expect(screen.getAllByRole('link').length).toBeLessThan(mockSearchVideo.items.length); - }) + }); it('Ao clicar em um video redireciona a pagina de display', async () => { const { history } = renderWithRouter(, { route: '/results/bugs' }); @@ -54,7 +54,7 @@ describe('Funcionalidades Componente Search Result', () => { await waitFor(() => expect(api.getVideoInfo).toHaveBeenCalled()); await waitFor(() => expect(api.getVideoComments).toHaveBeenCalled()); - + expect(screen.getByTestId('videoplayer')).toBeInTheDocument(); - }) -}) \ No newline at end of file + }); +}); diff --git a/src/api/localStorage.js b/src/api/localStorage.js index 7b8e547..b4b8498 100644 --- a/src/api/localStorage.js +++ b/src/api/localStorage.js @@ -1,7 +1,8 @@ export default function addToLocalStorage(key, value) { - const jsonKey = JSON.stringify(key); - if (!localStorage[jsonKey]) localStorage[jsonKey] = JSON.stringify([]); - const searchHistory = JSON.parse(localStorage[jsonKey]); - const updatedSearchHistory = [...searchHistory, value]; - localStorage[jsonKey] = JSON.stringify(updatedSearchHistory); + // const jsonKey = JSON.stringify(key); + if (!localStorage[key]) localStorage[key] = JSON.stringify([]); + const searchHistory = JSON.parse(localStorage[key]); + const historyArray = searchHistory.filter((element) => element !== value); + const updatedSearchHistory = [...historyArray, value]; + localStorage[key] = JSON.stringify(updatedSearchHistory); } diff --git a/src/api/service.js b/src/api/service.js index ea02130..a580372 100644 --- a/src/api/service.js +++ b/src/api/service.js @@ -43,3 +43,16 @@ export const getVideoComments = async (videoId) => { return error; } }; + +export const getRelatedVideos = async (videoId) => { + const urlParams = `part=snippet&relatedToVideoId=${videoId}&type=video&key=${YOUTUBE_AUTH_KEY()}`; + const URL = `${YOUTUBE_API_URL}/search?${urlParams}`; + + try { + const response = await fetch(URL); + const data = await response.json(); + return data; + } catch (error) { + return error; + } +}; diff --git a/src/components/Profile/ViewedVideos.js b/src/components/Profile/ViewedVideos.js new file mode 100644 index 0000000..9faf3d5 --- /dev/null +++ b/src/components/Profile/ViewedVideos.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import VideoCard from '../content/SearchResult/VideoCard/VideoCard'; +import '../../css/sideBar.css'; + +class ViewedVideos extends Component { + constructor(props) { + super(props); + + this.state = { data: [] }; + } + + componentDidMount() { + this.mountViewedComponent(); + } + + mountViewedComponent() { + const data = new Set(JSON.parse(localStorage.getItem('viewedVideos') || '[]')); + return this.setState({ data: [...data] }); + } + + render() { + const { data } = this.state; + if (!data) return

Sem vídeos favoritos

; + return ( +
+ {data.map((item) => ( + + + + ))} +
+ ); + } +} + +export default ViewedVideos; diff --git a/src/components/Profile/index.js b/src/components/Profile/index.js new file mode 100644 index 0000000..ee1bc25 --- /dev/null +++ b/src/components/Profile/index.js @@ -0,0 +1,23 @@ +import React, { Component } from 'react'; +import ViewedVideos from './ViewedVideos'; + +class Profile extends Component { + render() { + return
+
+

Vídeos Pesquisados

+
+
+ +
+
+

Vídeos Favoritos

+
+
+

Assistir mais tarde

+
+
; + } +} + +export default Profile; diff --git a/src/components/content/SearchResult/VideoCard/VideoCard.js b/src/components/content/SearchResult/VideoCard/VideoCard.js index 7cb82ef..a3efb85 100644 --- a/src/components/content/SearchResult/VideoCard/VideoCard.js +++ b/src/components/content/SearchResult/VideoCard/VideoCard.js @@ -7,8 +7,15 @@ class VideoCard extends Component { return !!(props === param1 || props === param2); } - render() { + constructor(props) { + super(props); const { video } = this.props; + + this.state = { video }; + } + + render() { + const { video } = this.state; const { id: { kind }, snippet } = video; const { thumbnails: { medium: { url } }, channelTitle, description, title } = snippet; return ( diff --git a/src/components/content/SearchResult/index.js b/src/components/content/SearchResult/index.js index 5201187..90ef9fa 100644 --- a/src/components/content/SearchResult/index.js +++ b/src/components/content/SearchResult/index.js @@ -46,10 +46,7 @@ class SearchResult extends Component { diff --git a/src/components/content/VideoPage/VideoPage.js b/src/components/content/VideoPage/VideoPage.js index 6c6b2df..9a35ee1 100644 --- a/src/components/content/VideoPage/VideoPage.js +++ b/src/components/content/VideoPage/VideoPage.js @@ -5,20 +5,22 @@ import VideoPlayerDescription from './VideoPlayer/VideoPlayerDescription'; import VideoPlayerInfo from './VideoPlayer/VideoPlayerInfo'; import VideoPlayerComments from './VideoPlayerComments/VideoPlayerComments'; import VideoSideBar from './VideoSideBar/VideoSideBar'; -import { getVideoInfo, getVideoComments } from '../../../api/service'; +import { + getVideoInfo, + getVideoComments, + getRelatedVideos, +} from '../../../api/service'; +import addToLocalStorage from '../../../api/localStorage'; class VideoPage extends Component { constructor(props) { super(props); - const { - match: { params: { videoId } }, - location: { state: { data } }, - } = this.props; + const { match: { params: { videoId } } } = this.props; this.state = { videoId, - relatedVideos: data, + relatedVideos: [], shouldRedirect: false, videoInfo: null, videoComments: null, @@ -28,23 +30,35 @@ class VideoPage extends Component { } componentDidMount() { + const { location: { state: { item } } } = this.props; + addToLocalStorage('viewedVideos', item); this.mountVideoPage(); } - handleSelectedVideo(videoId) { + handleSelectedVideo(videoId, video) { this.setState({ videoId }, () => { getVideoInfo(videoId).then((data) => this.setState({ videoInfo: data.items[0] })); getVideoComments(videoId).then((data) => this.setState({ videoComments: data.items })); + getRelatedVideos(videoId).then((data) => this.setState({ + relatedVideos: data + .items + .slice(0, 24), + })); }); - + addToLocalStorage('viewedVideos', video); return this.setState({ shouldRedirect: true }); } mountVideoPage() { const { videoId } = this.state; - this.setState({ shouldRedirect: false }); getVideoInfo(videoId).then((data) => this.setState({ videoInfo: data.items[0] })); getVideoComments(videoId).then((data) => this.setState({ videoComments: data.items })); + getRelatedVideos(videoId).then((data) => this.setState({ + relatedVideos: data + .items + .slice(0, 24), + })); + this.setState({ shouldRedirect: false }); } renderVideoPage(videoId, videoInfo, videoComments, relatedVideos) { diff --git a/src/components/content/VideoPage/VideoPlayer/VideoPlayer.js b/src/components/content/VideoPage/VideoPlayer/VideoPlayer.js index b90e78b..ff2d81c 100644 --- a/src/components/content/VideoPage/VideoPlayer/VideoPlayer.js +++ b/src/components/content/VideoPage/VideoPlayer/VideoPlayer.js @@ -5,7 +5,7 @@ import '../../../../css/chanelInfo.css'; class VideoPlayer extends Component { render() { const { embedId, title } = this.props; - const playerURL = `https://www.youtube.com/embed/${embedId}`; + const playerURL = `https://www.youtube.com/embed/${embedId}?autoplay=1`; return (