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)
mongoose.connect(config.MONGODB_URI)
mongoose
.connect(config.MONGODB_URI)
.then(() => {
logger.info('connected to MongoDB')
})
@@ -33,6 +34,11 @@ app.use('/api/blogs', blogsRouter)
app.use('/api/users', usersRouter)
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.errorHandler)

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": {
"start": "cross-env NODE_ENV=production node 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": "",
"license": "ISC",

View File

@@ -4,6 +4,7 @@ module.exports = {
browser: true,
es6: true,
'jest/globals': true,
'cypress/globals': true
},
extends: ['eslint:recommended', 'plugin:react/recommended'],
parserOptions: {
@@ -13,7 +14,7 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['react', 'jest'],
plugins: ['react', 'jest', 'cypress'],
rules: {
indent: ['error', 2],
'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",
"eject": "react-scripts eject",
"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": {
"extends": [
@@ -41,6 +43,8 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"cypress": "^12.15.0",
"eslint-plugin-cypress": "^2.13.3",
"eslint-plugin-jest": "^27.2.2",
"nodemon": "^2.0.22"
},

View File

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

View File

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

View File

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