Skip to content

Instantly share code, notes, and snippets.

@dwhite96
Last active January 1, 2018 20:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dwhite96/2ca1901605eb52fe937969c251e290cc to your computer and use it in GitHub Desktop.
Save dwhite96/2ca1901605eb52fe937969c251e290cc to your computer and use it in GitHub Desktop.
The code in this gist is for the Grow and Swap trade offer functionality including model, controller, views, and RSpec tests. Also included is code for the React.js chat component on the trade offer show page. This chat component uses PubNub for chat messaging services.
<span id="accepted_status">
<p>
<strong>Status:</strong>
<%= trade_offer.accepted_status %>
</p>
<% if trade_offer.accepted? %>
<%=
link_to "Unaccept trade offer",
accept_trade_offer_path(trade_offer.id,
"trade_offer[accepted]" => false),
remote: true, method: :put
%> |
<% else %>
<%=
link_to "Accept trade offer",
accept_trade_offer_path(trade_offer.id,
"trade_offer[accepted]" => true),
remote: true, method: :put
%> |
<% end %>
</span>
<div class="ui form">
<%= form_for(@trade_offer) do |f| %>
<% if @trade_offer.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@trade_offer.errors.count, "error") %> prohibited this trade_offer from being saved:</h2>
<ul>
<% @trade_offer.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<p>
<strong>Garden item:</strong>
<%= @garden_item.produce_item.name %><br>
<strong>Category:</strong>
<%= @garden_item.produce_item.category %><br>
<strong>Available Quantity:</strong>
<%= @garden_item.quantity %>
</p>
<div class="field">
<%= f.hidden_field :garden_item_id, value: params[:garden_item_id] %>
</div>
<div class="field">
<%= f.hidden_field :reciprocal_trade_offer_id, value: params[:reciprocal_trade_offer_id] %>
</div>
<div class="field">
<%= f.label :quantity %><br>
<%= f.number_field :quantity, min: 1, max: @garden_item.quantity %>
</div>
<div class="actions">
<%= f.submit "Submit" %>
</div>
<% end %>
</div>
$("#accepted_status").html("<%= escape_javascript(render partial: 'accept', locals: { trade_offer: @trade_offer }) %>");
import PropTypes from 'prop-types'
import React from 'react'
import TextareaAutosize from 'react-textarea-autosize'
import PubNub from 'pubnub'
import './chat.scss'
export default class Chat extends React.Component {
static propTypes = {
channel: PropTypes.string.isRequired,
messages: PropTypes.object.isRequired,
newMessage: PropTypes.string.isRequired,
actions: PropTypes.object.isRequired
}
constructor(props) {
super(props)
this.renderMessages = this.renderMessages.bind(this)
this.timeOfMessage = this.timeOfMessage.bind(this)
this.onNewMessageChange = this.onNewMessageChange.bind(this)
this.onNewMessageKeyPress = this.onNewMessageKeyPress.bind(this)
this.onSubmitClick = this.onSubmitClick.bind(this)
this.submitNewMessage = this.submitNewMessage.bind(this)
this.fetchHistory = this.fetchHistory.bind(this)
}
renderMessages() {
if (this.props.messages.messageList.length > 0) {
return this.props.messages.messageList.map(m => {
return (
<div className="message" key={m.entry.id}>
{m.entry.message}
{this.timeOfMessage(m.entry)}
</div>
)
})
}
else {
return <div className="no-messages">No messages</div>
}
}
timeOfMessage(message) {
return (
<div className="right">
{new Date(message.id).toLocaleTimeString()}
</div>
)
}
onNewMessageChange(event) {
this.props.actions.changeNewMessage(event.target.value)
}
onNewMessageKeyPress(event) {
if (event.key === 'Enter') {
this.submitNewMessage(event.target.value)
}
}
onSubmitClick() {
this.submitNewMessage(this.props.newMessage)
this.refs.messageInput.focus()
}
submitNewMessage(msg) {
const messageObj = {
id: Date.now(),
message: msg
}
this.pubnub.publish(
{
message: messageObj,
channel: this.props.channel
},
function (status, response) {
if (status.error) {
// handle error
console.log(status)
} else {
console.log("message Published w/ timetoken", response.timetoken)
}
}
)
this.props.actions.submitMessage()
}
fetchHistory() {
this.pubnub.history({
channel: this.props.channel,
count: 30,
start: this.props.messages.lastMessageTimestamp,
}).then((response) => {
// response is Array(3), where index 0 is an array of messages
// and index 1 and 2 are start and end dates of the messages
this.props.actions.addHistory(response.messages, response.startTimeToken)
})
}
componentDidMount() {
this.pubnub = new PubNub({
publishKey: "pub-c-7e4cb727-12f0-4f11-8d63-29b1a27bfe10",
subscribeKey: "sub-c-c28a8948-f585-11e6-bb94-0619f8945a4f",
ssl: true
})
this.pubnub.addListener({
message: (message) => {
this.props.actions.incomingMessage(message.message)
}
})
this.pubnub.subscribe({
channels: [this.props.channel]
})
this.fetchHistory()
}
componentDidUpdate() {
this.refs.messages.scrollTop = this.refs.messages.scrollHeight
}
render() {
return (
<div className="chat-component">
<div className="messages" ref="messages">{this.renderMessages()}</div>
<div className="message-input">
<TextareaAutosize
minRows={1}
maxRows={6}
wrap="hard"
type="text"
placeholder="Enter message here"
value={this.props.newMessage}
onChange={this.onNewMessageChange}
onKeyPress={this.onNewMessageKeyPress}
/>
<div className="message-button">
<button onClick={this.onSubmitClick}>Submit</button>
</div>
</div>
</div>
)
}
}
// Simple example of a React "smart" component
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Chat from '../components/Chat'
import * as actionCreators from '../actions/chatActionCreators'
// Which part of the Redux global state does our component want to receive as props?
const mapStateToProps = (state) => ({
channel: state.channel,
messages: state.messages,
newMessage: state.newMessage
})
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(actionCreators, dispatch),
})
// Don't forget to actually use connect!
// Note that we don't export Chat, but the redux "connected" version of it.
// See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
export default connect(mapStateToProps, mapDispatchToProps)(Chat)
import { combineReducers } from 'redux'
import {
CHANGE_NEW_MESSAGE,
SUBMIT_NEW_MESSAGE,
ADD_HISTORY,
INCOMING_NEW_MESSAGE
} from '../constants/chatConstants'
const channel = (state = '', action) => {
return state
}
const initialState = {
messageList: [],
lastMessageTimestamp: null
}
const messages = (state = initialState, action) => {
switch(action.type) {
case ADD_HISTORY:
let newMessageList = state.messageList
newMessageList.unshift(...action.payload.messages)
return {
...state,
messageList: newMessageList,
lastMessageTimestamp: action.payload.timestamp
}
case INCOMING_NEW_MESSAGE:
return {
...state,
messageList: state.messageList.concat(action.payload)
}
default:
return state
}
}
const newMessage = (state = '', action) => {
switch(action.type) {
case CHANGE_NEW_MESSAGE:
return action.payload.newValue
case SUBMIT_NEW_MESSAGE:
return state = ''
default:
return state
}
}
const railsContext = (state = {}, action) => {
return state
}
const chatReducer = combineReducers({
channel,
messages,
newMessage,
railsContext
})
export default chatReducer
<div class="ui inverted vertical masthead center aligned green segment">
<div class="ui text container">
<h1 class="ui inverted header">
<h1>Trade Offers Made</h1>
</h1>
</div>
</div>
<table id="trade_offers_made_table" class="ui green celled table">
<thead>
<tr>
<th>Item I want</th>
<th>Quantity</th>
<th>Trade offer made to</th>
<th>Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @trade_offers_made.each do |trade_offer| %>
<tr>
<td><%= trade_offer.garden_item.produce_item.name %></td>
<td><%= trade_offer.quantity %></td>
<td><%= trade_offer.garden_item.seller.full_name %></td>
<td><%= trade_offer.accepted_status %></td>
<td><%= link_to 'Show', trade_offer %></td>
<td><%=
link_to 'Edit', edit_trade_offer_path(trade_offer,
garden_item_id: trade_offer.garden_item.id)
%></td>
<td><%=
link_to 'Destroy', trade_offer,
method: :delete, data: { confirm: 'Are you sure?' }
%></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<div class="ui hidden section divider"></div>
<div class="ui inverted vertical masthead center aligned green segment">
<div class="ui text container">
<h1 class="ui inverted header">
<h1>Trade Offers Received</h1>
</h1>
</div>
</div>
<table id="trade_offers_received_table" class="ui green celled table">
<thead>
<tr>
<th>Item wanted by others</th>
<th>Quantity</th>
<th>Trade offer made from</th>
<th>Status</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @trade_offers_received.each do |trade_offer| %>
<tr>
<td><%= trade_offer.garden_item.produce_item.name %></td>
<td><%= trade_offer.quantity %></td>
<td><%= trade_offer.buyer.full_name %></td>
<td><%= trade_offer.accepted_status %></td>
<td><%= link_to 'Show', trade_offer %></td>
<td><%=
link_to 'Edit', edit_trade_offer_path(trade_offer,
garden_item_id: trade_offer.garden_item.id)
%></td>
<td><%=
link_to 'Destroy', trade_offer,
method: :delete, data: { confirm: 'Are you sure?' }
%></td>
</tr>
<% end %>
</tbody>
</table>
<div class="ui two column centered grid">
<% if current_user?(@trade_offer.buyer) %>
<div class="column">
<h1>Item you want:</h1>
<p>
<strong>Garden item:</strong>
<%= @trade_offer.garden_item.produce_item.name %>
</p>
<p>
<strong>Quantity:</strong>
<%= @trade_offer.quantity %>
</p>
<p>
<strong>Status:</strong>
<%= @trade_offer.accepted_status %>
</p>
<%=
link_to "Edit", edit_trade_offer_path(
@trade_offer, garden_item_id: @trade_offer.garden_item.id)
%> |
<%=
link_to "Destroy", @trade_offer,
method: :delete, data: { confirm: "Are you sure?" }
%>
</div>
<div class="column">
<% if @reciprocal_trade_offer.nil? %>
<h1>No reciprocal trade offer at this time.</h1>
<% else %>
<h1>Item <%= @reciprocal_trade_offer.buyer.full_name %> wants:</h1>
<p>
<strong>Garden item:</strong>
<%= @reciprocal_trade_offer.garden_item.produce_item.name %>
</p>
<p>
<strong>Quantity:</strong>
<%= @reciprocal_trade_offer.quantity %>
</p>
<%= render partial: "accept", locals: { trade_offer: @reciprocal_trade_offer } %>
<%=
link_to "Destroy", @reciprocal_trade_offer,
method: :delete, data: { confirm: "Are you sure?" }
%>
<% end %>
</div>
<% else %>
<div class="column">
<% if @reciprocal_trade_offer.nil? %>
<h1>You have not made a reciprocal trade offer.</h1>
<%= link_to "See #{@trade_offer.buyer.first_name}'s garden items",
user_path(
@trade_offer.buyer.id,
reciprocal_trade_offer_id: @trade_offer.id
) %>
<% else %>
<h1>Item you want:</h1>
<p>
<strong>Garden item:</strong>
<%= @reciprocal_trade_offer.garden_item.produce_item.name %>
</p>
<p>
<strong>Quantity:</strong>
<%= @reciprocal_trade_offer.quantity %>
</p>
<p>
<strong>Status:</strong>
<%= @reciprocal_trade_offer.accepted_status %>
</p>
<%=
link_to "Edit", edit_trade_offer_path(
@reciprocal_trade_offer, garden_item_id: @reciprocal_trade_offer.garden_item.id)
%> |
<%=
link_to "Destroy", @reciprocal_trade_offer,
method: :delete, data: { confirm: "Are you sure?" }
%>
<% end %>
</div>
<div class="column">
<h1>Item <%= @trade_offer.buyer.full_name %> wants:</h1>
<p>
<strong>Garden item:</strong>
<%= @trade_offer.garden_item.produce_item.name %>
</p>
<p>
<strong>Quantity:</strong>
<%= @trade_offer.quantity %>
</p>
<%= render partial: "accept", locals: { trade_offer: @trade_offer } %>
<%=
link_to "Destroy", @trade_offer,
method: :delete, data: { confirm: "Are you sure?" }
%>
</div>
<% end %>
</div>
<br>
<div class="ui section divider"></div>
<h3>Your messages for this trade offer:</h3>
<%= react_component("ChatApp") %>
<%= link_to "Back", trade_offers_path %>
class TradeOffer < ActiveRecord::Base
validates :quantity, presence: true
# The buyer creates the trade offer when they want a garden item
belongs_to :buyer, class_name: "User", foreign_key: :user_id
belongs_to :garden_item, foreign_key: :garden_item_id
# The seller receives a trade offer for a garden item the buyer wants
has_one :seller, class_name: "User", through: :garden_item
# The reciprocal trade offer is the opposite trade offer that makes up a
# trade deal. It exists only if a second trade offer was made. There is
# a max of two trade offers in a trade deal both of which should have a
# reciprocal trade offer.
belongs_to :reciprocal_trade_offer, class_name: "TradeOffer",
foreign_key: :reciprocal_trade_offer_id
validates_presence_of :buyer, class_name: "User", foreign_key: :user_id
validates_presence_of :garden_item, foreign_key: :garden_item_id
def reciprocated?
self.reciprocal_trade_offer_id != nil
end
def accepted_status
self.accepted? ? "Accepted" : "Not accepted"
end
end
require 'rails_helper'
RSpec.describe TradeOffer, type: :model do
let(:trade_offer) { trade_offer = build_stubbed(:trade_offer) }
let(:reciprocal_trade_offer) do
reciprocal_trade_offer = build_stubbed(:trade_offer_with_reciprocal_trade_offer)
end
describe "validations" do
it { is_expected.to validate_presence_of :quantity }
it { is_expected.to validate_presence_of :buyer }
it { is_expected.to validate_presence_of :garden_item }
end
describe "associations" do
it "belongs to buyer" do
is_expected.to belong_to(:buyer).
class_name("User").
with_foreign_key(:user_id)
end
it "belongs to garden item" do
is_expected.to belong_to(:garden_item).with_foreign_key(:garden_item_id)
end
it "has one seller through garden item" do
is_expected.to have_one(:seller).
class_name("User").
through(:garden_item)
end
it "belongs to reciprocal trade offer" do
is_expected.to belong_to(:reciprocal_trade_offer).
class_name("TradeOffer").
with_foreign_key(:reciprocal_trade_offer_id)
end
end
describe "#reciprocated?" do
it "returns false if there is no reciprocal trade offer" do
expect(trade_offer.reciprocated?).to be false
end
it "returns true if there is no reciprocal trade offer" do
expect(reciprocal_trade_offer.reciprocated?).to be true
end
end
describe "#accepted_status" do
it "returns 'Accepted' if trade offer is accepted" do
trade_offer = build_stubbed(:trade_offer, accepted: true)
expect(trade_offer.accepted_status).to eq "Accepted"
end
it "returns 'Not accepted' if trade offer is not accepted" do
expect(trade_offer.accepted_status).to eq "Not accepted"
end
end
end
class TradeOffersController < ApplicationController
before_action :set_trade_offer, only: [:show, :update, :accept, :destroy]
before_action :logged_in_user
def index
@trade_offers_made = current_user.trade_offers_made
@trade_offers_received = current_user.trade_offers_received
end
def show
@reciprocal_trade_offer = @trade_offer.reciprocal_trade_offer
# Need to know the id of the initiating trade offer so that the React Chat
# component PubNub channel is always the same regardless of which trade
# offer is clicked on -- initial or reciprocal.
if @reciprocal_trade_offer.nil?
initial_trade_offer_id = @trade_offer.id
else
initial_trade_offer_id = [@trade_offer.id, @reciprocal_trade_offer.id].min
end
# Initial state for react_on_rails component
@chat_props = { channel: "trade_offer_#{initial_trade_offer_id}",
messages: { messageList: [], lastMessageTimestamp: nil },
newMessage: ""
}
redux_store("chatStore", props: @chat_props)
end
def new
@garden_item = GardenItem.find(params[:garden_item_id])
@trade_offer = TradeOffer.new(garden_item_id: @garden_item.id)
end
def edit
@garden_item = GardenItem.find(params[:garden_item_id])
@trade_offer = TradeOffer.find(params[:id])
end
def create
@trade_offer = current_user.trade_offers_made.build(trade_offer_params)
@trade_offer.garden_item = GardenItem.find(params[:trade_offer][:garden_item_id].to_i)
if session[:reciprocal_trade_offer_id].present?
@reciprocal_trade_offer = TradeOffer.find(session[:reciprocal_trade_offer_id])
@trade_offer.reciprocal_trade_offer = @reciprocal_trade_offer
end
respond_to do |format|
if @trade_offer.save
format.html { redirect_to @trade_offer, notice: 'Trade offer was successfully created.' }
format.json { render :show, status: :created, location: @trade_offer }
else
format.html { render :new }
format.json { render json: @trade_offer.errors, status: :unprocessable_entity }
end
end
if session[:reciprocal_trade_offer_id].present?
@reciprocal_trade_offer.update(reciprocal_trade_offer_id: @trade_offer.id)
session[:reciprocal_trade_offer_id] = nil
end
end
def update
respond_to do |format|
if @trade_offer.update(trade_offer_params)
format.html { redirect_to @trade_offer, notice: 'Trade offer was successfully updated.' }
format.json { render :show, status: :ok, location: @trade_offer }
else
format.html { render :edit }
format.json { render json: @trade_offer.errors, status: :unprocessable_entity }
end
end
end
def accept
respond_to do |format|
if @trade_offer.update_attribute(:accepted, params[:trade_offer][:accepted])
format.js {}
else
format.html { redirect_to :back }
format.json { render json: @trade_offer.errors, status: :unprocessable_entity }
end
end
end
def destroy
@trade_offer.destroy
respond_to do |format|
format.html { redirect_to trade_offers_url, notice: 'Trade offer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_trade_offer
@trade_offer = TradeOffer.find(params[:id])
end
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in"
redirect_to login_path
end
end
def trade_offer_params
params.require(:trade_offer).permit(
:quantity, :accepted, :user_id, :garden_item_id, :reciprocal_trade_offer_id
)
end
end
require 'rails_helper'
RSpec.describe TradeOffersController, type: :controller do
let(:trade_offer) { trade_offer = create(:trade_offer) }
let(:reciprocal_trade_offer) { reciprocal_trade_offer = create(:reciprocal_trade_offer) }
let(:trade_offers) { trade_offers = create_list(:trade_offer, 2) }
# This should return the minimal set of attributes required to create a valid
# TradeOffer. As you add validations to TradeOffer, be sure to
# adjust the attributes here as well.
let(:valid_attributes) { {
quantity: 8,
user_id: trade_offer.buyer.id,
garden_item_id: trade_offer.garden_item_id
} }
let(:invalid_attributes) { {
quantity: nil,
user_id: trade_offer.buyer.id,
garden_item_id: trade_offer.garden_item_id
} }
# This should return the minimal set of values that should be in the session
# in order to pass any filters (e.g. authentication) defined in
# TradeOffersController. Be sure to keep this updated too.
let(:valid_session) { login(trade_offer.buyer) }
context "when not logged in" do
describe "GET index" do
it "redirects new to login form" do
get :index
expect(response).to redirect_to login_path
end
end
describe "GET show" do
it "redirects new to login form" do
get :show, id: reciprocal_trade_offer.to_param
expect(response).to redirect_to login_path
end
end
end
context "when logged in" do
describe "GET index" do
it "loads the page successfully" do
get :index, params: {}, session: valid_session
expect(response).to be_success
end
end
describe "GET show" do
it "assigns the requested trade_offer as @trade_offer" do
get :show, id: trade_offer.to_param, session: valid_session
expect(assigns(:trade_offer)).to eq(trade_offer)
end
end
describe "GET new" do
it "returns a success response" do
get :new, garden_item_id: trade_offer.garden_item_id.to_param, session: valid_session
expect(response).to be_success
end
it "assigns a new trade_offer as @trade_offer" do
get :new, garden_item_id: trade_offer.garden_item_id.to_param, session: valid_session
expect(assigns(:trade_offer)).to be_a_new(TradeOffer)
end
end
describe "GET edit" do
it "assigns the requested trade_offer as @trade_offer" do
get :edit, id: trade_offer, garden_item_id: trade_offer.garden_item_id, session: valid_session
expect(assigns(:trade_offer)).to eq(trade_offer)
expect(assigns(:garden_item)).to eq(trade_offer.garden_item)
end
end
describe "POST create" do
context "with valid params" do
it "creates a new TradeOffer" do
expect {
post :create, trade_offer: valid_attributes
}.to change(TradeOffer, :count).by(1)
end
it "assigns a newly created trade_offer as @trade_offer" do
post :create, trade_offer: valid_attributes, session: valid_session
expect(assigns(:trade_offer)).to be_a(TradeOffer)
expect(assigns(:trade_offer)).to be_persisted
end
it "redirects to the created trade_offer" do
post :create, trade_offer: valid_attributes, session: valid_session
expect(response).to redirect_to(TradeOffer.last)
end
end
context "with invalid params" do
it "assigns a newly created but unsaved trade_offer as @trade_offer" do
post :create, trade_offer: invalid_attributes, session: valid_session
expect(assigns(:trade_offer)).to be_a_new(TradeOffer)
end
it "re-renders the 'new' template" do
post :create, trade_offer: invalid_attributes, session: valid_session
expect(response).to render_template("new")
end
end
end
describe "PUT update" do
describe "with valid params" do
let(:new_attributes) { {
quantity: 13,
accepted: true,
user_id: trade_offer.buyer.id,
garden_item_id: trade_offer.garden_item.id
} }
it "updates the requested trade_offer" do
trade_offer
put :update, id: trade_offer.to_param, trade_offer: new_attributes, session: valid_session
trade_offer.reload
end
it "assigns the requested trade_offer as @trade_offer" do
trade_offer
put :update, id: trade_offer.to_param, trade_offer: new_attributes, session: valid_session
expect(assigns(:trade_offer)).to eq(trade_offer)
end
it "redirects to the trade_offer" do
trade_offer
put :update, id: trade_offer.to_param, trade_offer: new_attributes, session: valid_session
expect(response).to redirect_to(trade_offer)
end
end
describe "with invalid params" do
it "assigns the trade_offer as @trade_offer" do
put :update, id: trade_offer.to_param, trade_offer: invalid_attributes, session: valid_session
expect(assigns(:trade_offer)).to eq(trade_offer)
end
it "re-renders the 'edit' template" do
put :update, id: trade_offer.to_param, trade_offer: invalid_attributes, session: valid_session
expect(response).to render_template("edit")
end
end
end
describe "DELETE destroy" do
it "destroys the requested trade_offer" do
trade_offer
expect {
delete :destroy, id: trade_offer.to_param, session: valid_session
}.to change(TradeOffer, :count).by(-1)
end
it "redirects to the trade_offers list" do
delete :destroy, id: trade_offer.to_param, session: valid_session
expect(response).to redirect_to(trade_offers_path)
end
end
end
end
require 'rails_helper'
RSpec.feature TradeOffer, type: :feature do
given(:user) { user = create(:user) }
given(:garden_item) { garden_item = create(:garden_item) }
given(:trade_offer) { trade_offer = create(:trade_offer_with_reciprocal_trade_offer) }
context "when not logged in" do
scenario "guest is redirected to login page when clicking on 'New trade offer'" do
visit garden_item_path(garden_item.id)
click_link "New trade offer"
expect(page).to have_text "Please log in"
end
end
context "when logged in" do
background :each do
log_in_with(trade_offer.buyer)
end
scenario "user can create trade offer" do
visit garden_item_path(garden_item.id)
click_link "New trade offer"
fill_in "Quantity", with: 6
click_button "Submit"
expect(page).to have_text("Trade offer was successfully created.")
end
xscenario "user cannot enter more than quantity available when creating trade offer" do
visit garden_item_path(garden_item.id)
click_link "New trade offer"
fill_in "Quantity", with: 8
click_button "Submit"
expect(page).to have_text("Value must be less than or equal to #{garden_item.quantity}.")
end
scenario "user can view their list of open trade offers" do
visit root_path
click_link "My trade offers"
expect(page).to have_text("Trade Offers Made")
end
scenario "user can view an open trade offer" do
visit trade_offers_path
click_link "Show"
expect(page).to have_text("Item you want:")
end
background :each do
visit trade_offer_path(trade_offer.id)
end
scenario "user can edit trade offer" do
visit trade_offer_path(trade_offer.id)
click_link("Edit", match: :first)
fill_in "Quantity", with: 3
click_button "Submit"
expect(page).to have_text("Trade offer was successfully updated.")
end
scenario "user can delete trade offer" do
click_link("Destroy", match: :first)
expect(page).to have_text("Trade offer was successfully destroyed.")
end
xscenario "user can accept reciprocal trade offer", js: true do
click_link("Accept")
visit trade_offers_path
within("trade_offers_made_table") do
expect(page).to have_text("Accepted")
end
end
xscenario "user can reject trade offer" do
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment