Upload 5.16
This commit is contained in:
462
part5/bloglist-frontend/package-lock.json
generated
462
part5/bloglist-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,6 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@testing-library/user-event": "^13.5.0",
|
|
||||||
"axios": "^1.2.3",
|
"axios": "^1.2.3",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@@ -18,7 +15,8 @@
|
|||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"eslint": "eslint ."
|
"eslint": "eslint .",
|
||||||
|
"dev": "NODE_ENV=development nodemon index.js"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -40,6 +38,11 @@
|
|||||||
},
|
},
|
||||||
"proxy": "http://localhost:3001/",
|
"proxy": "http://localhost:3001/",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-plugin-jest": "^27.2.2"
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
}
|
"@testing-library/react": "^14.0.0",
|
||||||
|
"@testing-library/user-event": "^14.4.3",
|
||||||
|
"eslint-plugin-jest": "^27.2.2",
|
||||||
|
"nodemon": "^2.0.22"
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useState, useEffect, useRef } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import Blog from './components/Blog'
|
import Blog from './components/Blog.js'
|
||||||
import { BlogForm } from './components/BlogForm'
|
import { BlogForm } from './components/BlogForm.js'
|
||||||
import Notification from './components/Notification'
|
import Notification from './components/Notification.js'
|
||||||
import blogService from './services/blogs'
|
import blogService from './services/blogs.js'
|
||||||
import loginService from './services/login'
|
import loginService from './services/login.js'
|
||||||
import Togglable from './components/Togglable'
|
import Togglable from './components/Togglable.js'
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [blogs, setBlogs] = useState([])
|
const [blogs, setBlogs] = useState([])
|
||||||
@@ -24,12 +24,12 @@ const App = () => {
|
|||||||
setSuccessMessage(message)
|
setSuccessMessage(message)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSuccessMessage(null)
|
setSuccessMessage(null)
|
||||||
}, 5000)
|
}, 3000)
|
||||||
} else if (type === 'error') {
|
} else if (type === 'error') {
|
||||||
setErrorMessage(message)
|
setErrorMessage(message)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setErrorMessage(null)
|
setErrorMessage(null)
|
||||||
}, 5000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ const App = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={username}
|
||||||
name="Username"
|
name="username"
|
||||||
onChange={({ target }) => setUsername(target.value)}
|
onChange={({ target }) => setUsername(target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +122,7 @@ const App = () => {
|
|||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
name="Password"
|
name="password"
|
||||||
onChange={({ target }) => setPassword(target.value)}
|
onChange={({ target }) => setPassword(target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import blogService from '../services/blogs'
|
import blogService from '../services/blogs.js'
|
||||||
import Togglable from './Togglable'
|
import Togglable from './Togglable.js'
|
||||||
|
|
||||||
const Blog = ({ blog, setNotification, removeBlog }) => {
|
const Blog = ({ blog, setNotification, removeBlog }) => {
|
||||||
const blogStyle = {
|
const blogStyle = {
|
||||||
@@ -26,13 +26,26 @@ const Blog = ({ blog, setNotification, removeBlog }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findUser = async (blogId) => {
|
||||||
|
const blogs = await blogService.getAll()
|
||||||
|
const blogToFind = blogs.filter((blog) => blog.user).find((blog) => blog.id === blogId)
|
||||||
|
|
||||||
|
blog.user = blogToFind?.user
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blog.user) {
|
||||||
|
findUser(blog.id)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={blogStyle}>
|
<div style={blogStyle} className="blog">
|
||||||
<div>
|
<div>
|
||||||
<em>{blog.title}</em>
|
<em>
|
||||||
<Togglable buttonLabel="View">
|
{blog.title} by {blog.author}
|
||||||
|
</em>
|
||||||
|
<Togglable buttonLabel="View" className="blogButton">
|
||||||
<ul>
|
<ul>
|
||||||
<li> Author: {blog.author}</li>
|
<li>User: {blog.user?.name || 'Unknown' }</li>
|
||||||
<li> URL: {blog.url}</li>
|
<li> URL: {blog.url}</li>
|
||||||
<li>
|
<li>
|
||||||
{likes} likes
|
{likes} likes
|
||||||
|
|||||||
88
part5/bloglist-frontend/src/components/Blog.test.js
Normal file
88
part5/bloglist-frontend/src/components/Blog.test.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import '@testing-library/jest-dom/extend-expect'
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
import { Blog } from './Blog'
|
||||||
|
import { BlogForm } from './BlogForm'
|
||||||
|
|
||||||
|
const testSubject = {
|
||||||
|
blog: {
|
||||||
|
id: '62e5309ba56f29d013ebe184',
|
||||||
|
author: 'Andrew Trieu',
|
||||||
|
title: 'How to be a good programmer',
|
||||||
|
url: 'https://lmgtfy.app/',
|
||||||
|
likes: 10000000,
|
||||||
|
user: {
|
||||||
|
username: 'andyyyyy',
|
||||||
|
name: 'Andrew Trieu',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('test for blogs', () => {
|
||||||
|
test('blog\'s title and author is rendered, but not url or likes', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Blog blog={testSubject.blog}/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const div = container.querySelector('.blog')
|
||||||
|
expect(div).toHaveTextContent('How to be a good programmer by Andrew Trieu')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('blog\'s url and likes are rendered when button is clicked', async () => {
|
||||||
|
render(<Blog blog={testSubject.blog}/>)
|
||||||
|
|
||||||
|
const user = userEvent.setup()
|
||||||
|
const button = screen.getByText('How to be a good programmer by Andrew Trieu')
|
||||||
|
await user.click(button)
|
||||||
|
|
||||||
|
const url = screen.getByText('https://lmgtfy.app/')
|
||||||
|
expect(url).toBeDefined()
|
||||||
|
|
||||||
|
const likes = screen.getByText('10000000')
|
||||||
|
expect(likes).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('clicking the like button twice calls event handler twice', async () => {
|
||||||
|
// Blog does not have updateBlog function
|
||||||
|
const mockHandler = jest.fn()
|
||||||
|
|
||||||
|
render(<Blog blog={testSubject.blog}/>)
|
||||||
|
|
||||||
|
const user = userEvent.setup()
|
||||||
|
const button = screen.getByText('How to be a good programmer by Andrew Trieu')
|
||||||
|
await user.click(button)
|
||||||
|
|
||||||
|
const likeButton = screen.getByText('+')
|
||||||
|
await user.click(likeButton)
|
||||||
|
await user.click(likeButton)
|
||||||
|
|
||||||
|
// 3 times because the blog is viewed once and the like button is clicked twice
|
||||||
|
expect(mockHandler.mock.calls).toHaveLength(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('new blog form calls event handler with correct details', async () => {
|
||||||
|
const createBlog = jest.fn()
|
||||||
|
|
||||||
|
const component = render(
|
||||||
|
<BlogForm createBlog={createBlog}/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const user = userEvent.setup()
|
||||||
|
|
||||||
|
const title = component.container.getElementsByName('title')
|
||||||
|
const author = component.container.getElementsByName('author')
|
||||||
|
const url = component.container.getElementsByName('urlAddress')
|
||||||
|
const form = component.container.getElementsByName('form')
|
||||||
|
|
||||||
|
await user.type(title, 'How to be a good programmer')
|
||||||
|
await user.type(author, 'Andrew Trieu')
|
||||||
|
await user.type(url, 'https://lmgtfy.app/')
|
||||||
|
await user.submit(form)
|
||||||
|
|
||||||
|
expect(createBlog.mock.calls).toHaveLength(1)
|
||||||
|
expect(createBlog.mock.calls[0][0].title).toBe('How to be a good programmer')
|
||||||
|
expect(createBlog.mock.calls[0][0].author).toBe('Andrew Trieu')
|
||||||
|
expect(createBlog.mock.calls[0][0].url).toBe('https://lmgtfy.app/')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -24,7 +24,7 @@ export const BlogForm = ({ createBlog }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2> Create new blog</h2>
|
<h2> Create new blog</h2>
|
||||||
<form onSubmit={addBlog}>
|
<form onSubmit={addBlog} name='form'>
|
||||||
<div>
|
<div>
|
||||||
Title:
|
Title:
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import App from './App'
|
import App from './App.js'
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
|
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
|
||||||
Reference in New Issue
Block a user