Commit fcaab1a3 by Anil Kumar

create project module and write some test cases for it

parent ebb60b88
about
active_storage:install
app:template
app:update
assets:clean[keep]
assets:clobber
assets:environment
assets:precompile
cache_digests:dependencies
cache_digests:nested_dependencies
db:create
db:drop
db:environment:set
db:fixtures:load
db:migrate
db:migrate:status
db:rollback
db:schema:cache:clear
db:schema:cache:dump
db:schema:dump
db:schema:load
db:seed
db:setup
db:structure:dump
db:structure:load
db:version
dev:cache
initializers
log:clear
middleware
notes
notes:custom
restart
routes
secret
spec
spec:features
spec:models
spec:requests
stats
time:zones[country_or_offset]
tmp:clear
tmp:create
webpacker
webpacker:binstubs
webpacker:check_binstubs
webpacker:check_node
webpacker:check_yarn
webpacker:clean[keep]
webpacker:clobber
webpacker:compile
webpacker:info
webpacker:install
webpacker:install:angular
webpacker:install:coffee
webpacker:install:elm
webpacker:install:erb
webpacker:install:react
webpacker:install:stimulus
webpacker:install:svelte
webpacker:install:typescript
webpacker:install:vue
webpacker:verify_install
webpacker:yarn_install
yarn:install
class Api::V1::ProjectsController < ApplicationController
def index
projects = Project.all
render json: projects
end
def create
project = Project.create!(project_params)
if project
render json: project
else
render json: project.errors
end
end
def show
if project
puts project
render json: project
else
render json: project.errors
end
end
def destroy
project&.destroy
render json: { message: 'Project deleted' }
end
def update
if project.update(project_params)
render json: project
else
render json: project.errors
end
end
def project_params
params.permit(:title, :description, :employee_id)
end
def project
@project ||= Project.find(params[:id])
end
end
\ No newline at end of file
import React from 'react';
import { Link } from "react-router-dom";
class AddProject extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: '',
employee_id: '',
employeeList: []
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
onSubmit(event) {
event.preventDefault();
const url = "/api/v1/projects/create";
const { title, description, employee_id } = this.state;
if (title.length == 0 || description.length == 0 || employee_id == "0")
return;
const body = {
title,
description,
employee_id
};
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch(url, {
method: "POST",
headers: {
"X-CSRF-Token": token,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(response => this.props.history.push(`/project/${response.id}`))
.catch(error => console.log(error.message));
}
componentDidMount() {
const url = "/api/v1/employees/index";
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(response => this.setState({ employeeList: response }))
.catch(() => this.props.history.push("/"));
}
render() {
const employeeList = this.state.employeeList;
const employeeSelection = employeeList.map((employee) => (
<option value={employee.id}>{employee.firstName + ' ' + employee.lastName}</option>
));
return (
<div className="container mt-5">
<h1 className="font-weight-normal text-center mb-5">
Add New Project Details
</h1>
<form id="projectForm" onSubmit={this.onSubmit}>
<div className="form-row">
<div className="form-group col-md-6">
<label htmlFor="title">Project Title</label>
<input type="text" className="form-control" id="title" name="title" placeholder="Project title" onChange={this.onChange} required />
</div>
<div className="form-group col-md-6">
<label htmlFor="employee_id">Assign Project to employee</label>
<select className="form-control" id="employee_id" name="employee_id" onChange={this.onChange}>
<option value="0">--Select Employee--</option>
{employeeSelection}
</select>
</div>
</div>
<div className="form-group">
<label htmlFor="description">Project Description</label>
<textarea type="text" className="form-control" id="description" name="description" placeholder="Write Description" onChange={this.onChange} required ></textarea>
</div>
<button type="submit" className="btn btn-primary">Add Project</button>
</form>
<Link to="/projects" className="btn btn-link mt-3">
Back to Projects
</Link>
</div>
);
}
}
export default AddProject;
\ No newline at end of file
...@@ -45,7 +45,7 @@ class Employees extends React.Component { ...@@ -45,7 +45,7 @@ class Employees extends React.Component {
const noEmployee = ( const noEmployee = (
<div className="vw-100 vh-50 d-flex align-items-center justify-content-center"> <div className="vw-100 vh-50 d-flex align-items-center justify-content-center">
<h4> <h4>
No Employee yet. Why not <Link to="/new_enployee">create one</Link> No Employee yet. Why not <Link to="/employee">create one</Link>
</h4> </h4>
</div> </div>
); );
......
...@@ -27,6 +27,14 @@ export default () => ( ...@@ -27,6 +27,14 @@ export default () => (
> >
View Employees View Employees
</Link> </Link>
<br></br>
<Link
to="/projects"
className="btn btn-lg custom-button"
role="button"
>
View Projects
</Link>
</div> </div>
</div> </div>
</div> </div>
......
import React from 'react';
import { Link } from "react-router-dom";
class Project extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
description: '',
employee_id: '',
project: {},
employeeList: {}
}
// this.onChange = this.onChange.bind(this);
this.addHtmlEntities = this.addHtmlEntities.bind(this);
this.deleteProject = this.deleteProject.bind(this);
// this.getEmployees = this.getEmployees.bind(this);
}
componentDidMount() {
const {
match: {
params: { id }
}
} = this.props;
const url = `/api/v1/project/show/${id}`;
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(response => this.setState({ project: response }))
.catch(() => this.props.history.push("/projects"));
// this.getEmployees()
}
// onChange(event) {
// this.setState({ [event.target.name]: event.target.value });
// }
addHtmlEntities(str) {
return String(str)
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">");
}
// getEmployees() {
// const url = "/api/v1/employees/index";
// fetch(url)
// .then(response => {
// if (response.ok) {
// return response.json();
// }
// throw new Error("Network response was not ok.");
// })
// .then(response => this.setState({ employeeList: response }))
// .catch(error => console.log(error.message));
// }
// editProject() {
// event.preventDefault();
// const url = "/api/v1/projects/update";
// const { title, description, employee_id } = this.state;
// if (title.length == 0 || description.length == 0 || employee_id == "0")
// return;
// const body = {
// title,
// description,
// employee_id
// };
// const token = document.querySelector('meta[name="csrf-token"]').content;
// fetch(url, {
// method: "POST",
// headers: {
// "X-CSRF-Token": token,
// "Content-Type": "application/json"
// },
// body: JSON.stringify(body)
// })
// .then(response => {
// if (response.ok) {
// return response.json();
// }
// throw new Error("Network response was not ok.");
// })
// .then(response => this.props.history.push(`/project/${response.id}`))
// .catch(error => console.log(error.message));
// }
deleteProject() {
const {
match: {
params: { id }
}
} = this.props;
const url = `/api/v1/project/destroy/${id}`;
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch(url, {
method: "DELETE",
headers: {
"X-CSRF-Token": token,
"Content-Type": "application/json"
}
})
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(() => this.props.history.push("/projects"))
.catch(error => console.log(error.message));
}
render() {
const { project } = this.state;
// const employeeList = this.state.employeeList;
// const employeeList = this.state.employeeList;
// console.log(employeeList);
// let employeeId = project.employee_id
// let options = '';
// for (var i = 0; i < employeeList.length; i++) {
// let employee = employeeList[i];
// if (employee.id == employeeId)
// options += `<option value=` + employee.id + ` selected>` + employee.firstName + ' ' + employee.lastName + `</option>`
// else
// options += `<option value=` + employee.id + `>` + employee.firstName + ' ' + employee.lastName + `</option>`
// }
// const employeeSelection = employeeList.map(employee => (
// <option value={employee.id}>{employee.firstName + ' ' + employee.lastName}</option>
// ));
return (
<>
<section className="jumbotron jumbotron-fluid text-center">
<div className="container py-5">
<h1 className="display-4">Project Details of {project.title}</h1>
</div>
</section>
<div className="container">
<div className="row">
<div className="col-md-6">Project Name: </div>
<div className="col-md-6">{project.title}</div>
</div>
<div className="row">
<div className="col-md-6">Project Description:</div>
<div className="col-md-6">{project.description}</div>
</div>
</div>
<div className="container py-5">
<div className="row">
<div className="col-sm-12 col-lg-2">
{/* <button type="button" className="btn btn-primary" data-toggle="modal" data-target="#editModal">
Edit Details
</button> */}
<button type="button" className="btn btn-danger" onClick={this.deleteProject}>
Delete Project
</button>
</div>
</div>
<Link to="/projects" className="btn btn-link">
Back to Projects
</Link>
</div>
{/* <div className="modal fade" id="editModal">
<div className="modal-dialog modal-lg">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">Edit Project Details</h4>
<button type="button" className="close" data-dismiss="modal">&times;</button>
</div>
<div className="modal-body">
<form onSubmit={this.editProject}>
<div className="form-row">
<div className="form-group col-md-6">
<label htmlFor="title">Project Title</label>
<input type="text" className="form-control" id="title" name="title" placeholder="Project title" value={project.title} onChange={this.onChange} required />
</div>
<div className="form-group col-md-6">
<label htmlFor="employee_id">Assign Project to employee</label>
<select className="form-control" id="employee_id" name="employee_id" onChange={this.onChange}>
<option value="0">--Select Employee--</option>
{options}
</select>
</div>
</div>
<div className="form-group">
<label htmlFor="description">Project Description</label>
<textarea type="text" className="form-control" id="description" name="description" placeholder="Write Description" value={project.description} onChange={this.onChange} required ></textarea>
</div>
<button type="submit" className="btn btn-primary">Edit Project</button>
</form>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div> */}
</>
)
}
}
export default Project;
\ No newline at end of file
import React from "react";
import { Link } from "react-router-dom";
class Projects extends React.Component {
constructor(props) {
super(props);
this.state = {
projects: []
};
}
componentDidMount() {
const url = "/api/v1/projects/index";
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok.");
})
.then(response => this.setState({ projects: response }))
.catch(() => this.props.history.push("/"));
}
render() {
const { projects } = this.state;
const allProjects = projects.map((project, index) => (
<div key={index} className="col-md-6 col-lg-4">
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">{project.title}</h5>
<Link to={`/project/${project.id}`} className="btn custom-button">
View Project
</Link>
</div>
</div>
</div>
));
const noProject = (
<div className="vw-100 vh-50 d-flex align-items-center justify-content-center">
<h4>
No Project yet. <Link to="/project">Add New Project</Link>
</h4>
</div>
);
return (
<>
<section className="jumbotron jumbotron-fluid text-center">
<div className="container py-5">
<h1 className="display-4">Projects</h1>
</div>
</section>
<div className="py-5">
<main className="container">
<div className="text-right mb-3">
<Link to="/project" className="btn custom-button">
Add New Project
</Link>
</div>
<div className="row">
{projects.length > 0 ? allProjects : noProject}
</div>
<Link to="/" className="btn btn-link">
Home
</Link>
</main>
</div>
</>
);
}
}
export default Projects;
\ No newline at end of file
...@@ -7,6 +7,9 @@ import NewRecipe from "../components/NewRecipe"; ...@@ -7,6 +7,9 @@ import NewRecipe from "../components/NewRecipe";
import Employees from "../components/Employees"; import Employees from "../components/Employees";
import Employee from "../components/Employee"; import Employee from "../components/Employee";
import NewEmployee from "../components/NewEmployee"; import NewEmployee from "../components/NewEmployee";
import Projects from "../components/Projects";
import Project from "../components/Project";
import AddProject from "../components/AddProject";
export default ( export default (
<Router> <Router>
...@@ -18,6 +21,9 @@ export default ( ...@@ -18,6 +21,9 @@ export default (
<Route path="/employees" exact component={Employees} /> <Route path="/employees" exact component={Employees} />
<Route path="/employee/:id" exact component={Employee} /> <Route path="/employee/:id" exact component={Employee} />
<Route path="/employee" exact component={NewEmployee} /> <Route path="/employee" exact component={NewEmployee} />
<Route path="/projects" exact component={Projects} />
<Route path="/project/:id" exact component={Project} />
<Route path="/project" exact component={AddProject} />
</Switch> </Switch>
</Router> </Router>
); );
\ No newline at end of file
class Project < ApplicationRecord
belongs_to :employee, optional: true
validates :title, presence: true
validates :description, presence: true
end
...@@ -17,6 +17,16 @@ Rails.application.routes.draw do ...@@ -17,6 +17,16 @@ Rails.application.routes.draw do
post 'recipes/uploadFile' post 'recipes/uploadFile'
end end
end end
namespace :api do
namespace :v1 do
get 'projects/index'
post 'projects/create'
post 'project/update'
get 'project/show/:id', to: 'projects#show'
delete 'project/destroy/:id', to: 'projects#destroy'
end
end
root 'homepage#index' root 'homepage#index'
get '/*path' => 'homepage#index' get '/*path' => 'homepage#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
......
class CreateProjects < ActiveRecord::Migration[5.2]
def change
create_table :projects do |t|
t.string :title
t.string :description
t.belongs_to :employee, foreign_key: true
t.timestamps
end
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_09_30_024252) do ActiveRecord::Schema.define(version: 2020_10_02_085711) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -27,6 +27,15 @@ ActiveRecord::Schema.define(version: 2020_09_30_024252) do ...@@ -27,6 +27,15 @@ ActiveRecord::Schema.define(version: 2020_09_30_024252) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
create_table "projects", force: :cascade do |t|
t.string "title"
t.string "description"
t.bigint "employee_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["employee_id"], name: "index_projects_on_employee_id"
end
create_table "recipes", force: :cascade do |t| create_table "recipes", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.text "ingredients", null: false t.text "ingredients", null: false
...@@ -36,4 +45,5 @@ ActiveRecord::Schema.define(version: 2020_09_30_024252) do ...@@ -36,4 +45,5 @@ ActiveRecord::Schema.define(version: 2020_09_30_024252) do
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
add_foreign_key "projects", "employees"
end end
FactoryBot.define do
factory :project do
title { 'Wemlo' }
description {'This is for testing'}
employee_id { 5 }
end
factory :random_project, class: Project do
title {Faker::Name.name}
description {Faker::Lorem.sentence}
end
end
\ No newline at end of file
require 'rails_helper'
RSpec.feature "Projects", type: :feature do
context 'Add New Project' do
before do
visit 'http://localhost:3000/project'
end
scenario 'Should be successful', :js do
within('form') do
fill_in 'Title', with: 'MCMS'
fill_in 'Description', with: 'This project is for testing'
find("#employee_id option[value='2']").select_option
end
click_button 'Add Project'
expect(page).to have_content('Projects')
end
# scenario 'Should failed' do
# within(find('form')) do
# fill_in 'Title', with: 'MCMS'
# fill_in 'Description', with: ''
# find("#employee_id option[value='0']").select_option
# end
# end
end
context 'Update Project' do
end
context 'Delete User' do
end
end
...@@ -39,10 +39,10 @@ RSpec.describe Employee, type: :model do ...@@ -39,10 +39,10 @@ RSpec.describe Employee, type: :model do
expect(employee).to eq(true) expect(employee).to eq(true)
end end
# it 'Delete Data Test' do it 'Delete Data Test' do
# employee = Employee.delete(firstName: 'vv') employee = Employee.delete(firstName: 'vv')
# expect(employee).to eq(true) expect(employee).to eq(0)
# end end
end end
# pending "add some examples to (or delete) #{__FILE__}" # pending "add some examples to (or delete) #{__FILE__}"
......
require 'rails_helper'
RSpec.describe Project, type: :model do
context "Validation Test Case" do
let(:project) { build(:project) }
it 'title validation' do
project.title = nil
expect(project.save).to eq(false)
end
it 'decription validation' do
project.description = nil
expect(project.save).to eq(false)
end
end
context "Project Operations" do
it 'add project detail' do
project = Project.create!(title: 'Wemlo', description: 'this is for testing').save
expect(project).to eq(true)
end
end
end
...@@ -5,6 +5,7 @@ require File.expand_path('../config/environment', __dir__) ...@@ -5,6 +5,7 @@ require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production # Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production? abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails' require 'rspec/rails'
require 'capybara/rails'
# Add additional requires below this line. Rails is not loaded until this point! # Add additional requires below this line. Rails is not loaded until this point!
# Requires supporting ruby files with custom matchers and macros, etc, in # Requires supporting ruby files with custom matchers and macros, etc, in
......
require 'rails_helper'
RSpec.describe "Api::V1::Projects", type: :request do
describe "GET /index" do
it "returns http success" do
get("/api/v1/projects/index")
# data = JSON.parse(response.body)
expect(response.status).to eq(200)
end
end
describe "POST /create" do
it "returns http success" do
get "/api/v1/projects/create"
expect(response).to have_http_status(:success)
end
end
# describe "GET /show" do
# it "returns http success" do
# get "/api/v1/project/show/:1"
# # data = JSON.parse(response.body)
# expect(response).to eq(200)
# end
# end
describe "DELETE /destroy" do
it "returns http success" do
get "/api/v1/project/destroy"
expect(response.status).to eq(200)
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment