Last active
January 1, 2018 20:30
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$("#accepted_status").html("<%= escape_javascript(render partial: 'accept', locals: { trade_offer: @trade_offer }) %>"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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 %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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