Upload 5.23

This commit is contained in:
Andrew Trieu
2023-06-22 16:11:10 +03:00
parent 24a4e4cc2f
commit d73c3d8d48
15 changed files with 2545 additions and 33 deletions

View File

@@ -14,7 +14,8 @@ mongoose.set('strictQuery', false)
logger.info('connecting to', config.MONGODB_URI) logger.info('connecting to', config.MONGODB_URI)
mongoose.connect(config.MONGODB_URI) mongoose
.connect(config.MONGODB_URI)
.then(() => { .then(() => {
logger.info('connected to MongoDB') logger.info('connected to MongoDB')
}) })
@@ -33,7 +34,12 @@ app.use('/api/blogs', blogsRouter)
app.use('/api/users', usersRouter) app.use('/api/users', usersRouter)
app.use('/api/login', loginRouter) app.use('/api/login', loginRouter)
if (process.env.NODE_ENV === 'test') {
const testingRouter = require('./controllers/testing')
app.use('/api/testing', testingRouter)
}
app.use(middleware.unknownEndpoint) app.use(middleware.unknownEndpoint)
app.use(middleware.errorHandler) app.use(middleware.errorHandler)
module.exports = app module.exports = app

View File

@@ -0,0 +1,12 @@
const testingRouter = require('express').Router()
const Blog = require('../models/blog')
const User = require('../models/user')
testingRouter.post('/reset', async (request, response) => {
await Blog.deleteMany({})
await User.deleteMany({})
response.status(204).end()
})
module.exports = testingRouter

View File

@@ -6,7 +6,9 @@
"scripts": { "scripts": {
"start": "cross-env NODE_ENV=production node index.js", "start": "cross-env NODE_ENV=production node index.js",
"dev": "cross-env NODE_ENV=development nodemon index.js", "dev": "cross-env NODE_ENV=development nodemon index.js",
"test": "cross-env NODE_ENV=test jest --verbose --runInBand" "test": "cross-env NODE_ENV=test jest --verbose --runInBand",
"start:test": "cross-env NODE_ENV=test node index.js"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",

View File

@@ -4,6 +4,7 @@ module.exports = {
browser: true, browser: true,
es6: true, es6: true,
'jest/globals': true, 'jest/globals': true,
'cypress/globals': true
}, },
extends: ['eslint:recommended', 'plugin:react/recommended'], extends: ['eslint:recommended', 'plugin:react/recommended'],
parserOptions: { parserOptions: {
@@ -13,7 +14,7 @@ module.exports = {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
}, },
plugins: ['react', 'jest'], plugins: ['react', 'jest', 'cypress'],
rules: { rules: {
indent: ['error', 2], indent: ['error', 2],
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],

View File

@@ -0,0 +1,9 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

View File

@@ -0,0 +1,102 @@
describe('blog app', function () {
beforeEach(function () {
cy.request('POST', 'http://localhost:3001/api/testing/reset')
cy.request('POST', 'http://localhost:3001/api/users/', {
name: 'Andrew',
username: 'andyyyyy',
password: '123456',
})
cy.request('POST', 'http://localhost:3001/api/users/', {
name: 'Matti Luukkainen',
username: 'mluukkai',
password: 'salainen',
})
cy.visit('http://localhost:3000')
})
it('front page can be opened', function () {
cy.contains('Bloglist')
cy.contains('Please log in')
cy.contains('Username')
cy.contains('Password')
cy.contains('Login')
})
describe('when logged in', function () {
beforeEach(function () {
it('login succeeds with correct credentials', function () {
cy.get('.username').type('andyyyyy')
cy.get('.password').type('123456')
cy.get('.loginButton').click()
})
cy.contains('Andrew logged in')
})
it('a new blog can be created', function () {
cy.contains('New blog').click()
cy.get('.title').type('a blog created')
cy.get('.author').type('cypress')
cy.get('.urlAddress').type('cypress.com')
cy.get('.createButton').click()
cy.contains('a blog created by cypress')
})
it('a blog can be liked', function () {
cy.contains('New blog').click()
cy.get('.title').type('a blog created')
cy.get('.author').type('cypress')
cy.get('.urlAddress').type('cypress.com')
cy.get('.createButton').click()
cy.contains('a blog created by cypress')
cy.contains('View').click()
cy.contains('+').click()
cy.contains('1')
})
it('a blog can be deleted', function () {
cy.contains('New blog').click()
cy.get('.title').type('a blog created')
cy.get('.author').type('cypress')
cy.get('.urlAddress').type('cypress.com')
cy.get('.createButton').click()
cy.contains('a blog created by cypress')
cy.contains('View').click()
cy.contains('Remove').click()
cy.get('html').should('not.contain', 'a blog created by cypress')
})
it('only the creator can delete a blog', function () {
cy.contains('New blog').click()
cy.get('.title').type('a blog created')
cy.get('.author').type('cypress')
cy.get('.urlAddress').type('cypress.com')
cy.get('.createButton').click()
cy.contains('a blog created by cypress')
cy.get('.logoutButton').click()
cy.get('.username').type('mluukkai')
cy.get('.password').type('salainen')
cy.get('.loginButton').click()
cy.contains('View').click()
cy.contains('Remove').click()
cy.contains('a blog created by cypress')
})
it('blogs are ordered according to likes', function () {
cy.contains('New blog').click()
cy.get('.title').type('another blog created')
cy.get('.author').type('cypress')
cy.get('.urlAddress').type('cypress.com')
cy.get('.createButton').click()
cy.contains('another blog created by cypress')
cy.contains('View').click()
cy.contains('+').click()
cy.wait(1000)
cy.contains('+').click()
cy.wait(1000)
cy.visit('http://localhost:3000')
cy.get('.blog').eq(0).should('contain', 'another blog created by cypress')
cy.get('.blog').eq(1).should('contain', 'a blog created by cypress')
})
})
})

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,9 @@
"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" "dev": "NODE_ENV=development nodemon index.js",
"cypress:open": "cypress open",
"start:test": "NODE_ENV=test node index.js"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@@ -41,6 +43,8 @@
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"cypress": "^12.15.0",
"eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-jest": "^27.2.2", "eslint-plugin-jest": "^27.2.2",
"nodemon": "^2.0.22" "nodemon": "^2.0.22"
}, },

View File

@@ -107,13 +107,13 @@ const App = () => {
} }
const loginForm = () => ( const loginForm = () => (
<form onSubmit={handleLogin}> <form onSubmit={handleLogin} className="loginForm">
<div> <div>
Username Username
<input <input
type="text" type="text"
value={username} value={username}
name="username" className="username"
onChange={({ target }) => setUsername(target.value)} onChange={({ target }) => setUsername(target.value)}
/> />
</div> </div>
@@ -122,11 +122,11 @@ const App = () => {
<input <input
type="password" type="password"
value={password} value={password}
name="password" className="password"
onChange={({ target }) => setPassword(target.value)} onChange={({ target }) => setPassword(target.value)}
/> />
</div> </div>
<button type="submit">Login</button> <button type="submit" className='loginButton'>Login</button>
</form> </form>
) )
@@ -134,19 +134,21 @@ const App = () => {
return ( return (
<div> <div>
<p> {user.name} logged in </p> <p> {user.name} logged in </p>
<button onClick={handleLogout}>Logout</button> <button onClick={handleLogout} className='logoutButton'>Logout</button>
<Togglable buttonLabel="New blog" ref={newBlogRef}> <Togglable buttonLabel="New blog" ref={newBlogRef}>
<BlogForm createBlog={handleNewBlog} /> <BlogForm createBlog={handleNewBlog} />
</Togglable> </Togglable>
<h2> All blogs</h2> <h2> All blogs</h2>
{blogs.map((blog) => ( {blogs
<Blog .sort((a, b) => b.likes - a.likes)
key={blog.id} .map((blog) => (
blog={blog} <Blog
setNotification={setNotification} key={blog.id}
removeBlog={handleRemoveBlog} blog={blog}
/> setNotification={setNotification}
))} removeBlog={handleRemoveBlog}
/>
))}
</div> </div>
) )
} }

View File

@@ -70,10 +70,10 @@ describe('test for blogs', () => {
const user = userEvent.setup() const user = userEvent.setup()
const title = component.container.getElementsByName('title') const title = component.container.querySelector('.title')
const author = component.container.getElementsByName('author') const author = component.container.querySelector('.author')
const url = component.container.getElementsByName('urlAddress') const url = component.container.querySelector('.urlAddress')
const form = component.container.getElementsByName('form') const form = component.container.querySelector('.form')
await user.type(title, 'How to be a good programmer') await user.type(title, 'How to be a good programmer')
await user.type(author, 'Andrew Trieu') await user.type(author, 'Andrew Trieu')

View File

@@ -24,13 +24,13 @@ export const BlogForm = ({ createBlog }) => {
return ( return (
<div> <div>
<h2> Create new blog</h2> <h2> Create new blog</h2>
<form onSubmit={addBlog} name='form'> <form onSubmit={addBlog} className='form'>
<div> <div>
Title: Title:
<input <input
type="text" type="text"
value={title} value={title}
name="title" className="title"
onChange={(event) => setTitle(event.target.value)} onChange={(event) => setTitle(event.target.value)}
/> />
</div> </div>
@@ -39,7 +39,7 @@ export const BlogForm = ({ createBlog }) => {
<input <input
type="text" type="text"
value={author} value={author}
name="author" className="author"
onChange={(event) => setAuthor(event.target.value)} onChange={(event) => setAuthor(event.target.value)}
/> />
</div> </div>
@@ -48,11 +48,11 @@ export const BlogForm = ({ createBlog }) => {
<input <input
type="text" type="text"
value={urlAddress} value={urlAddress}
name="urlAddress" className="urlAddress"
onChange={(event) => setUrlAddress(event.target.value)} onChange={(event) => setUrlAddress(event.target.value)}
/> />
</div> </div>
<button type="submit">Create</button> <button type="submit" className='createButton'>Create</button>
</form> </form>
</div> </div>
) )