diff --git a/cypress/e2e/spec.cy.js b/cypress/e2e/spec.cy.js index 322992c..f4f4858 100644 --- a/cypress/e2e/spec.cy.js +++ b/cypress/e2e/spec.cy.js @@ -1,5 +1,127 @@ -describe('template spec', () => { - it('passes', () => { - cy.visit('https://example.cypress.io') +//Need to login before testing +describe('API Tests', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/'); }) -}) \ No newline at end of file + it('Fetches and displays Wikipedia page contents', () => { + cy.intercept('GET', 'https://en.wikipedia.org/w/api.php?', { + statusCode: 200, + fixture: 'wikipediaApiResponse', + }).as('fetchContents'); + cy.wait('@fetchContents').then(({ response }) => { + expect(response.statusCode).to.equal(200); + }); + cy.contains('Loading...').should('not.exist'); + cy.contains('An error occurred').should('not.exist'); + }); + + it('Handles error when API fails', () => { + cy.intercept('GET', 'https://en.wikipedia.org/w/api.php?', { + statusCode: 500, + body: 'Server Error', + delayMs: 200, + }).as('fetchError'); + cy.wait('@fetchError').then(({ response }) => { + expect(response.statusCode).to.equal(500); + cy.contains('An error occurred').should('exist'); + }); + }); +}); + +describe('App Component', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/'); + }) + + it('Loads the app properly', () => { + cy.get('.header-text').should('be.visible'); + cy.get('.login-button').should('be.visible'); + cy.get('.random-headline').should('be.visible'); + }); + + it('Displays random controversies', () => { + cy.get('.result-name').should('contain', ''); + cy.get('.random-headline').should('contain', 'Random Controversy'); + cy.get('.card').should('be.visible'); + cy.get('.card') + .children() + .first() + .within(() => { + cy.contains('h2', ''); + cy.contains('p', ''); + cy.contains('button', '😡Save Controversy😡'); + cy.contains('button', '🤬Save as favorite controversy🤬'); + }); + }); +}); + +describe('Can search for a Controversy', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/'); + //placeholder for login flow + }); + + it('Searches for a term, then clears input', () => { + cy.get('input[type="text"]').should('be.visible'); + cy.get('input[type="text"]').type('SearchTerm{enter}'); + cy.get('input[type="text"]').should('have.text', ''); + }); + + it('Displays controversies for a search result', () => { + cy.get('.card').should('be.visible'); + cy.get('.result-name').should('contain', ''); + cy.get('.results-list') + .children() + .first() + .within(() => { + cy.contains('h2', ''); + cy.contains('p', ''); + cy.contains('button', '😡Save Controversy😡'); + cy.contains('button', '🤬Save as favorite controversy🤬'); + }); + cy.get('.results-list') + .children() + .last() + .within(() => { + cy.contains('h2', ''); + cy.contains('p', ''); + cy.contains('button', '😡Save Controversy😡'); + cy.contains('button', '🤬Save as favorite controversy🤬'); + }); + }); +}); + +describe('Card Component', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/'); + }) + + it('Renders snippet properly', () => { + cy.get('.card-content').should('be.visible'); + cy.get('h2').should('contain', ''); + cy.get('p').should('contain', ''); + }); + + it('Handles show more/show less functionality', () => { + + }); +}); + +describe('UserView Component', () => { + beforeEach(() => { + cy.visit('http://localhost:3000/'); + //login flow placeholder + }) + + it('Loads page content properly', () => { + cy.get('#profile').should('be.visible'); + cy.get('#profile').click(); + cy.visit('http://localhost:3000/Profile'); + cy.get('.filter-buttons').should('have.descendants', 'button'); + // cy.get('.article').should('be.visible'); + }); + + it('Saves controversy properly', () => { + + }); +}); \ No newline at end of file diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json deleted file mode 100644 index 02e4254..0000000 --- a/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/cypress/fixtures/sampleRandomCards.json b/cypress/fixtures/sampleRandomCards.json new file mode 100644 index 0000000..d95fbe8 --- /dev/null +++ b/cypress/fixtures/sampleRandomCards.json @@ -0,0 +1,3 @@ +{ + "need": "one sample result with multiple wiki cards" +} diff --git a/cypress/fixtures/wikipediaApiResponse.json b/cypress/fixtures/wikipediaApiResponse.json new file mode 100644 index 0000000..6bb143f --- /dev/null +++ b/cypress/fixtures/wikipediaApiResponse.json @@ -0,0 +1,18 @@ +{ + "query": { + "search": [ + { + "title": "Example Title 1", + "snippet": "This is an example snippet for the first result." + }, + { + "title": "Example Title 2", + "snippet": "Another example snippet for the second result." + }, + { + "title": "Example Title 3", + "snippet": "Another example snippet for the third result." + } + ] + } +} \ No newline at end of file diff --git a/src/App/App.js b/src/App/App.js index ae4acdf..0c3403e 100644 --- a/src/App/App.js +++ b/src/App/App.js @@ -7,6 +7,7 @@ import './App.css'; import Card from '../Card/Card.js'; import useSearchResults from '../hooks/useSearchResults.js'; import Profile from '../UserView/UserView.js'; +import ErrorBoundary from '../Errors/ErrorBoundary.js'; function App() { const { isLoading, isAuthenticated } = useAuth0(); @@ -44,38 +45,40 @@ function App() { } return ( -
-
- -

H8rAid!

- - {isAuthenticated && } - -
- - } /> - } /> - } /> - } /> - - {showRandomControversy && ( + +
+
+ +

H8rAid!

+ + {isAuthenticated && } + +
+ + } /> + } /> + } /> + } /> + + {showRandomControversy && (

Random Controversy

{controversies[0] &&

{initialResults.title}

}
- {controversies.map((item, i) => ( - saveControversy(item.parse.text["*"])} - onSaveAsFavorite={() => saveControversy(item.parse.text["*"], true)} - /> - ))} + {controversies.map((item, i) => ( + saveControversy(item.parse.text["*"])} + onSaveAsFavorite={() => saveControversy(item.parse.text["*"], true)} + /> + ))}
- )} -
+ )} +
+ ); }; diff --git a/src/Card/Card.js b/src/Card/Card.js index 49a04ae..2d7d6c5 100644 --- a/src/Card/Card.js +++ b/src/Card/Card.js @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import DOMPurify from 'dompurify'; import './Card.css'; import modifyRelativeUrls from '../hooks/modifyRelativeUrls'; +import PropTypes from "prop-types"; const Card = ({ snippet, onSave, onSaveAsFavorite }) => { const [showFullContent, setShowFullContent] = useState(false); @@ -23,4 +24,8 @@ const Card = ({ snippet, onSave, onSaveAsFavorite }) => { ); }; -export default Card; \ No newline at end of file +export default Card; + +Card.propTypes = { + snippet: PropTypes.string.isRequired, +}; \ No newline at end of file diff --git a/src/Errors/ErrorBoundary.js b/src/Errors/ErrorBoundary.js new file mode 100644 index 0000000..b26c6dd --- /dev/null +++ b/src/Errors/ErrorBoundary.js @@ -0,0 +1,32 @@ +import ErrorPage from '../Errors/Errors.js'; +import React, { Component } from 'react'; + +class ErrorBoundary extends Component { + constructor(error) { + super(error); + this.state = { + hasError: false, + errorMessage: '', + }; + } + + static getDerivedStateFromError(error) { + if (error.message === 'Network response was not ok') { + return { hasError: true, errorMessage: 'Error in SpecificComponent' }; + } + if(error.message === 'Error fetching Wikipedia page:') { + return{ hasError: true, errorMessage: 'Cannot fetch Wikipedia page'} + } + return { hasError: true, errorMessage: error.toString() }; + } + + componentsDidCatch(error, errorInfo) { + console.error('ErrorBoundary caught an error:', error, errorInfo); + } + + render() { + return this.state.hasError ? : this.props.children; + } +} + +export default ErrorBoundary; \ No newline at end of file diff --git a/src/Errors/Errors.js b/src/Errors/Errors.js new file mode 100644 index 0000000..84fde61 --- /dev/null +++ b/src/Errors/Errors.js @@ -0,0 +1,12 @@ +import React from 'react'; + +const errorMessage = ({ errorMessage }) => { + return ( +
+

Error Encountered

+

{errorMessage}

+
+ ); +}; + +export default errorMessage; \ No newline at end of file diff --git a/src/Main/Main.css b/src/Main/Main.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/UserView/UserView.css b/src/UserView/UserView.css index e69de29..5488cbf 100644 --- a/src/UserView/UserView.css +++ b/src/UserView/UserView.css @@ -0,0 +1,17 @@ +.profile { + flex-direction: column; + } + + .filter-buttons, .profile { + display: flex; + } + + .filter-buttons { + justify-content: center; + } + + @media screen and (max-width: 500px) { + .results-list { + flex-direction: column; + } + } \ No newline at end of file diff --git a/src/UserView/UserView.js b/src/UserView/UserView.js index cca6d65..c984943 100644 --- a/src/UserView/UserView.js +++ b/src/UserView/UserView.js @@ -1,5 +1,7 @@ import React, { useState } from 'react'; import Card from '../Card/Card'; +import PropTypes from "prop-types"; +import './UserView.css'; const Profile = ({ savedControversies }) => { const [showFavorites, setShowFavorites] = useState(false); @@ -39,4 +41,8 @@ const Profile = ({ savedControversies }) => { ); }; -export default Profile; \ No newline at end of file +export default Profile; + +Profile.propTypes = { + savedControversies: PropTypes.array.isRequired, +} \ No newline at end of file