From bbdc0001b4099a267e840d6e58b6635c35d7238e Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Thu, 16 Jul 2015 11:57:27 -0400 Subject: [PATCH] Initial attempt at comments --- Gemfile | 1 + Gemfile.lock | 3 + app/assets/javascripts/comments.coffee | 3 + app/assets/stylesheets/comments.scss | 3 + app/assets/stylesheets/custom.scss | 75 ++++--- app/controllers/comments_controller.rb | 62 ++++++ app/controllers/posts_controller.rb | 1 + app/helpers/application_helper.rb | 14 +- app/helpers/comments_helper.rb | 10 + app/models/comment.rb | 22 ++ app/models/post.rb | 12 ++ app/views/comments/_comment.html.slim | 13 ++ app/views/comments/_form.html.slim | 7 + app/views/comments/edit.html.slim | 2 + app/views/comments/new.html.slim | 0 app/views/comments/show.html.slim | 8 + app/views/layouts/application.html.slim | 2 +- app/views/posts/_post.html.slim | 14 ++ app/views/posts/show.html.slim | 27 +-- app/views/subcreddits/show.html.slim | 6 +- config/environments/development.rb | 7 + config/routes.rb | 4 +- db/migrate/20150716155403_create_comments.rb | 14 ++ ...150804000313_add_comments_count_to_post.rb | 10 + ...150804145405_add_deleted_at_to_comments.rb | 5 + db/schema.rb | 21 +- spec/controllers/comments_controller_spec.rb | 201 ++++++++++++++++++ spec/factories/comment_factory.rb | 7 + spec/factories/user_factory.rb | 2 +- spec/features/comments/edit_comment_spec.rb | 30 +++ spec/features/comments/new_comment_spec.rb | 57 +++++ spec/features/posts/edit_post_spec.rb | 3 +- spec/features/posts/lists_posts_spec.rb | 2 +- spec/helpers/comments_helper_spec.rb | 7 + spec/models/comment_spec.rb | 63 ++++++ spec/models/post_spec.rb | 31 +++ 36 files changed, 687 insertions(+), 62 deletions(-) create mode 100644 app/assets/javascripts/comments.coffee create mode 100644 app/assets/stylesheets/comments.scss create mode 100644 app/controllers/comments_controller.rb create mode 100644 app/helpers/comments_helper.rb create mode 100644 app/models/comment.rb create mode 100644 app/views/comments/_comment.html.slim create mode 100644 app/views/comments/_form.html.slim create mode 100644 app/views/comments/edit.html.slim create mode 100644 app/views/comments/new.html.slim create mode 100644 app/views/comments/show.html.slim create mode 100644 app/views/posts/_post.html.slim create mode 100644 db/migrate/20150716155403_create_comments.rb create mode 100644 db/migrate/20150804000313_add_comments_count_to_post.rb create mode 100644 db/migrate/20150804145405_add_deleted_at_to_comments.rb create mode 100644 spec/controllers/comments_controller_spec.rb create mode 100644 spec/factories/comment_factory.rb create mode 100644 spec/features/comments/edit_comment_spec.rb create mode 100644 spec/features/comments/new_comment_spec.rb create mode 100644 spec/helpers/comments_helper_spec.rb create mode 100644 spec/models/comment_spec.rb diff --git a/Gemfile b/Gemfile index 708250c..28204f4 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem 'bootstrap-sass' gem 'simple_form' gem 'friendly_id', '~> 5.1.0' +gem 'ancestry' gem 'bcrypt' diff --git a/Gemfile.lock b/Gemfile.lock index b076b4f..b2f291f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,6 +41,8 @@ GEM ice_nine (~> 0.11.0) memoizable (~> 0.4.0) addressable (2.3.8) + ancestry (2.1.0) + activerecord (>= 3.0.0) arel (6.0.0) arrayfields (4.9.2) ast (2.0.0) @@ -365,6 +367,7 @@ PLATFORMS ruby DEPENDENCIES + ancestry bcrypt better_errors binding_of_caller diff --git a/app/assets/javascripts/comments.coffee b/app/assets/javascripts/comments.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/comments.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/comments.scss b/app/assets/stylesheets/comments.scss new file mode 100644 index 0000000..e730912 --- /dev/null +++ b/app/assets/stylesheets/comments.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Comments controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index e6cdabb..cb36eb8 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -42,6 +42,10 @@ $navbar-default-link-hover-color: #369; } } +.links { + font-size: .8em; +} + .navbar { .logo { margin-top: 3px; @@ -49,32 +53,7 @@ $navbar-default-link-hover-color: #369; } } -.posts { - .post { - p { - margin-bottom: 1px; - } - - .title { - font-size: 1.2em; - } - - .details { - font-size: .8em; - } - - .links { - font-size: .8em; - } - - .content { - border: 1px solid #369; - background-color: #fafafa; - border-radius: 7px; // need to work all browsers - padding: 5px; - } - } - +.contents { ul { list-style: none; list-style-type: none; @@ -90,6 +69,38 @@ $navbar-default-link-hover-color: #369; li:first-child { padding-left: 0px; } + + p { + margin-bottom: 1px; + } + + .title { + font-size: 1.2em; + } + + .details { + font-size: .8em; + } +} + +.posts { + .post { + margin: 0 0 15px 0; + + .content { + margin: 4px 0 4px 0; + border: 1px solid #369; + background-color: #fafafa; + border-radius: 7px; // need to work all browsers + padding: 5px; + } + } +} + +.comments { + .comment { + margin: 15px 0 15px 0; + } } .navbar-custom { @@ -99,3 +110,15 @@ $navbar-default-link-hover-color: #369; .main { margin: 0 5px 0 5px; } + +.title { + font-size: 1.3em; +} + +.nested_comments { + margin-left: 50px; +} + +.in-page { + padding: 5px; +} diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..7e68555 --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,62 @@ +class CommentsController < ApplicationController + before_filter :set_comment, only: [:show, :edit, :update, :destroy] + before_filter :set_post + before_filter :set_subcreddit + + def show + @comments = @comment.subtree.arrange(order: :created_at) + end + + def new + @comment = Comment.new(params[:parent_id]) + end + + def create + @comment = @post.comments.build comment_params + @comment.user = current_user + + if @comment.save + flash[:notice] = 'Comment saved' + else + flash[:alert] = 'Comment could not be saved' + end + + redirect_to subcreddit_post_path(@subcreddit, @post) + end + + def edit + end + + def update + if @comment.update comment_params + redirect_to subcreddit_post_path(@subcreddit, @post), + notice: 'Comment updated' + else + render :edit + end + end + + def destroy + @comment.destroy + redirect_to subcreddit_post_path(@subcreddit, @post), + notice: 'Comment deleted' + end + + private + + def set_subcreddit + @subcreddit = Subcreddit.friendly.find(params[:subcreddit_id]) + end + + def set_post + @post = Post.find(params[:post_id]) + end + + def set_comment + @comment = Comment.find(params[:id]) + end + + def comment_params + params.require(:comment).permit(:parent_id, :content) + end +end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index e854a07..11c81f8 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -3,6 +3,7 @@ class PostsController < ApplicationController before_filter :set_subcreddit def show + @comments = @post.comments.arrange(order: :created_at) end def new diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 16b0f55..4b0e707 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,11 +6,11 @@ module ApplicationHelper private def bootstrap_classes - { - 'alert' => 'alert-warning', - 'error' => 'alert-danger', - 'notice' => 'alert-info', - 'success' => 'alert-success' - } -end + { + 'alert' => 'alert-warning', + 'error' => 'alert-danger', + 'notice' => 'alert-info', + 'success' => 'alert-success' + } + end end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 0000000..714dd93 --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,10 @@ +module CommentsHelper + def nested_comments(comments) + comments.map do |comment, sub_comments| + render(comment, post: comment.post, subcreddit: comment.post.subcreddit) + + content_tag(:div, + nested_comments(sub_comments), + class: 'nested_comments') + end.join.html_safe + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..011745d --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,22 @@ +class Comment < ActiveRecord::Base + has_ancestry + + belongs_to :user + belongs_to :post, counter_cache: true + + delegate :username, to: :user, prefix: true + + validates :content, presence: true + + def content + destroyed? ? '[deleted]' : read_attribute(:content) + end + + def destroy + update_attribute(:deleted_at, Time.now) + end + + def destroyed? + self.deleted_at != nil + end +end diff --git a/app/models/post.rb b/app/models/post.rb index ad4bb41..ed7ed7f 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -2,6 +2,8 @@ class Post < ActiveRecord::Base belongs_to :user belongs_to :subcreddit + has_many :comments + delegate :username, to: :user, prefix: true validates :title, @@ -12,6 +14,16 @@ class Post < ActiveRecord::Base length: { maximum: 15000 } def to_param + # This "just works" because of the way Rails IDs work. .to_i must be run on + # any incoming ID. "1-title-parameterized" will automatically be converted + # to 1 and "2-title-paramerterized-with-number-2" will automatically be + # converted to 2. This gives us desired functionality without adding code + # to properly handle retrieving based on slug. Hopefully, this does not + # cause issues later on. "#{self.id}-#{self.title.parameterize}" end + + def comments? + self.comments_count != 0 + end end diff --git a/app/views/comments/_comment.html.slim b/app/views/comments/_comment.html.slim new file mode 100644 index 0000000..edba596 --- /dev/null +++ b/app/views/comments/_comment.html.slim @@ -0,0 +1,13 @@ +.comment + p.details= "#{comment.user_username} X points #{distance_of_time_in_words comment.created_at, Time.now} ago" + p.content= comment.content + ul.links.list-inline + li= link_to 'permalink', subcreddit_post_comment_path(subcreddit, post, comment) + li= link_to 'save', '' + - if comment.parent + li= link_to 'parent', subcreddit_post_comment_path(subcreddit, post, comment.parent) + li= link_to 'edit', edit_subcreddit_post_comment_path(subcreddit, post, comment) + li= link_to 'delete', subcreddit_post_comment_path(subcreddit, post, comment), method: :delete + li= link_to 'spam', '' + li= link_to 'remove', '' + li= link_to 'give gold', '' diff --git a/app/views/comments/_form.html.slim b/app/views/comments/_form.html.slim new file mode 100644 index 0000000..e9fe262 --- /dev/null +++ b/app/views/comments/_form.html.slim @@ -0,0 +1,7 @@ += simple_form_for [subcreddit, post, comment] do |f| + - if local_assigns.has_key?(:parent) && parent + = f.hidden_field :parent_id, value: parent.id + .form-inputs + = f.input :content, label: false + .form-actions + = f.button :submit diff --git a/app/views/comments/edit.html.slim b/app/views/comments/edit.html.slim new file mode 100644 index 0000000..6ab2760 --- /dev/null +++ b/app/views/comments/edit.html.slim @@ -0,0 +1,2 @@ +h1 Edit comment +== render 'form', subcreddit: @subcreddit, post: @post, comment: @comment diff --git a/app/views/comments/new.html.slim b/app/views/comments/new.html.slim new file mode 100644 index 0000000..e69de29 diff --git a/app/views/comments/show.html.slim b/app/views/comments/show.html.slim new file mode 100644 index 0000000..b55d49f --- /dev/null +++ b/app/views/comments/show.html.slim @@ -0,0 +1,8 @@ +== render 'posts/post', post: @post +.alert.alert-info.in-page + p you are viewing a single comment's thread. + p #{link_to 'view the rest of the comments', subcreddit_post_path(@subcreddit, @post)} → += "Commenting as: #{current_user.username}" +== render 'comments/form', subcreddit: @subcreddit, post: @post, comment: @post.comments.build, parent: @comment +.comments.contents + == nested_comments(@comments) diff --git a/app/views/layouts/application.html.slim b/app/views/layouts/application.html.slim index cb2cfdd..54879d1 100644 --- a/app/views/layouts/application.html.slim +++ b/app/views/layouts/application.html.slim @@ -7,7 +7,7 @@ html = csrf_meta_tags body == render 'navbar' - .container + .container-fluid == render 'flash_messages' .main.container-fluid .col-md-3.pull-right diff --git a/app/views/posts/_post.html.slim b/app/views/posts/_post.html.slim new file mode 100644 index 0000000..8a1fa93 --- /dev/null +++ b/app/views/posts/_post.html.slim @@ -0,0 +1,14 @@ +.posts.contents + .post.show + p.title= link_to post.title, [post.subcreddit, post] + p.details= "submitted #{distance_of_time_in_words post.created_at, Time.now} ago by #{post.user_username}" + p.content= post.content + ul.links.list-inline + li= link_to "#{post.comments_count} comments", subcreddit_post_path(post.subcreddit, post) + li= link_to 'share', '' + li= link_to 'edit', edit_subcreddit_post_path(post.subcreddit, post) + li= link_to 'save', '' + li= link_to 'hide', '' + li= link_to 'remove', '' + li= link_to 'approve', '' + li= link_to 'nsfw', '' diff --git a/app/views/posts/show.html.slim b/app/views/posts/show.html.slim index 0addc8b..2a6c2e7 100644 --- a/app/views/posts/show.html.slim +++ b/app/views/posts/show.html.slim @@ -1,18 +1,9 @@ -.posts - .post.show - p.title= link_to @post.title, [@subcreddit, @post] - p.details= "submitted #{distance_of_time_in_words @post.created_at, Time.now} ago by #{@post.user_username}" - ul.links.list-inline - li= link_to 'XXX comments', '' - li= link_to 'source', '' - li= link_to 'share', '' - li= link_to 'save', '' - li= link_to 'hide', '' - li= link_to 'give gold', '' - li= link_to 'spam', '' - li= link_to 'remove', '' - li= link_to 'approve', '' - li= link_to 'report', '' - li= link_to 'nsfw', '' - li= link_to 'hide all child comments', '' - p.content= @post.content +== render 'post', post: @post +- if @post.comments? + .title= "all #{@post.comments_count} comments" +- else + .title= "no comments (yet)" += "Commenting as: #{current_user.username}" +== render 'comments/form', subcreddit: @subcreddit, post: @post, comment: @post.comments.build, parent: nil +.comments.contents + == nested_comments(@comments) diff --git a/app/views/subcreddits/show.html.slim b/app/views/subcreddits/show.html.slim index 7400463..4cea55a 100644 --- a/app/views/subcreddits/show.html.slim +++ b/app/views/subcreddits/show.html.slim @@ -1,13 +1,13 @@ - if @subcreddit.closed? = "Board has been closed" - else - .posts + .posts.contents ul - @subcreddit.posts.order('created_at DESC').each_with_index do |post, rank| li .post - p.title= link_to post.title, [@subcreddit, post] + p.title= link_to post.title, subcreddit_post_path(@subcreddit, post) p.details= "submitted #{distance_of_time_in_words post.created_at, Time.now} ago by #{post.user_username}" ul.links.list-inline - li= link_to 'XXX comments', '' + li= link_to "#{post.comments_count} comments", subcreddit_post_path(@subcreddit, post) li= link_to 'share', '' diff --git a/config/environments/development.rb b/config/environments/development.rb index b55e214..b80c574 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -38,4 +38,11 @@ Rails.application.configure do # Raises error for missing translations # config.action_view.raise_on_missing_translations = true + + # Bullet + config.after_initialize do + Bullet.enable = true + Bullet.bullet_logger = true + Bullet.add_footer = true + end end diff --git a/config/routes.rb b/config/routes.rb index f1d4a29..c5026cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,9 @@ Rails.application.routes.draw do get 'signout', to: 'user_sessions#destroy', as: :signout resources :subcreddits, path: 'c', except: [:destroy] do - resources :posts, except: [:index] + resources :posts, path: '', constraints: { id: /\d+\-.+/ }, except: [:index] do + resources :comments, path: '', constraints: { id: /\d+/ }, except: [:index] + end end resources :user_sessions, only: [:new, :create, :destroy] diff --git a/db/migrate/20150716155403_create_comments.rb b/db/migrate/20150716155403_create_comments.rb new file mode 100644 index 0000000..d45676a --- /dev/null +++ b/db/migrate/20150716155403_create_comments.rb @@ -0,0 +1,14 @@ +class CreateComments < ActiveRecord::Migration + def change + create_table :comments do |t| + t.references :user, index: true, foreign_key: true + t.references :post, index: true, foreign_key: true + t.string :ancestry + t.text :content + + t.timestamps null: false + end + + add_index :comments, :ancestry + end +end diff --git a/db/migrate/20150804000313_add_comments_count_to_post.rb b/db/migrate/20150804000313_add_comments_count_to_post.rb new file mode 100644 index 0000000..eeff512 --- /dev/null +++ b/db/migrate/20150804000313_add_comments_count_to_post.rb @@ -0,0 +1,10 @@ +class AddCommentsCountToPost < ActiveRecord::Migration + def change + add_column :posts, :comments_count, :integer, default: 0 + + Post.reset_column_information + Post.all.each do |p| + p.update_attribute :comments_count, p.comments.length + end + end +end diff --git a/db/migrate/20150804145405_add_deleted_at_to_comments.rb b/db/migrate/20150804145405_add_deleted_at_to_comments.rb new file mode 100644 index 0000000..afed931 --- /dev/null +++ b/db/migrate/20150804145405_add_deleted_at_to_comments.rb @@ -0,0 +1,5 @@ +class AddDeletedAtToComments < ActiveRecord::Migration + def change + add_column :comments, :deleted_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 7aa24e6..6295055 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,21 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150716050055) do +ActiveRecord::Schema.define(version: 20150804145405) do + + create_table "comments", force: :cascade do |t| + t.integer "user_id" + t.integer "post_id" + t.string "ancestry" + t.text "content" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.datetime "deleted_at" + end + + add_index "comments", ["ancestry"], name: "index_comments_on_ancestry" + add_index "comments", ["post_id"], name: "index_comments_on_post_id" + add_index "comments", ["user_id"], name: "index_comments_on_user_id" create_table "friendly_id_slugs", force: :cascade do |t| t.string "slug", null: false @@ -32,8 +46,9 @@ ActiveRecord::Schema.define(version: 20150716050055) do t.string "title" t.string "link" t.text "content" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "comments_count", default: 0 end add_index "posts", ["subcreddit_id"], name: "index_posts_on_subcreddit_id" diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb new file mode 100644 index 0000000..4047c6a --- /dev/null +++ b/spec/controllers/comments_controller_spec.rb @@ -0,0 +1,201 @@ +require 'rails_helper' + +describe CommentsController, type: :controller do + let!(:user) { build(:user) } + let!(:subcreddit) { create(:subcreddit) } + let!(:tpost) { create(:post, subcreddit: subcreddit) } + let(:data) { { content: 'Here is some updated content for a comment' } } + + before(:each) do + allow_any_instance_of(ApplicationController) + .to receive(:current_user).and_return(user) + end + + describe '#show' do + let!(:comment) { create(:comment) } + before(:each) do + get :show, + subcreddit_id: comment.post.subcreddit, + post_id: comment.post, + id: comment + end + + it 'should render :show' do + expect(response).to render_template(:show) + end + + it 'should assign correct Comment to @comment' do + expect(assigns(:comment)).to eq(comment) + end + end + + describe '#new' do + let(:comment) { build(:comment) } + before(:each) do + get :new, + subcreddit_id: comment.post.subcreddit, + post_id: comment.post + end + + it 'should render :new' do + expect(response).to render_template(:new) + end + + it 'should assign new Comment to @comment' do + expect(assigns(:comment)).to be_a_new(Comment) + end + end + + describe '#create' do + context 'with valid data' do + it 'should create a comment' do + expect do + post :create, + subcreddit_id: subcreddit, + post_id: tpost, + comment: data + end.to change(Comment, :count).by(1) + end + + it 'should redirect to the parent post' do + expect(post :create, + subcreddit_id: subcreddit, + post_id: tpost, + comment: data + ).to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, + assigns(:post))) + end + + it 'should send a notice flash message' do + expect(post :create, + subcreddit_id: subcreddit, + post_id: tpost, + comment: data) + + expect(flash[:notice]).to be_present + end + end + + context 'with invalid data' do + before(:each) { data['content'] = '' } + + it 'should not create a new comment' do + expect do + post :create, + subcreddit_id: subcreddit, + post_id: tpost, + comment: data + end.to change(Comment, :count).by(0) + end + + it 'should render :new' do + expect(post :create, + subcreddit_id: subcreddit, + post_id: tpost, + comment: data + ).to redirect_to(subcreddit_post_path(subcreddit, tpost)) + end + end + end + + describe '#edit' do + let!(:comment) { create(:comment) } + before(:each) do + get :edit, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit + end + + context 'with valid comment' do + it 'should render :edit' do + expect(response).to render_template(:edit) + end + + it 'should assign correct Comment to @comment' do + expect(assigns(:comment)).to eq(comment) + end + end + end + + context '#update' do + let!(:comment) { create(:comment) } + let(:data) { { content: 'Some edited comment content goes here' } } + + context 'with valid data' do + before(:each) do + put :update, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit, + comment: data + end + + it 'should assign correct Comment to @comment' do + expect(assigns(:comment)).to eq(comment) + end + + it 'should update the comment' do + comment.reload + + expect(comment.content).to eq(data[:content]) + end + + it 'should redirect to the post' do + expect(response) + .to redirect_to(subcreddit_post_path(assigns(:post).subcreddit, + assigns(:post))) + end + + it 'should display a notice flash message' do + expect(flash[:notice]).to be_present + end + end + + context 'with invalid data' do + before(:each) { data[:content] = '' } + + it 'should render :edit' do + put :update, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit, + comment: data + + expect(response).to render_template(:edit) + end + end + end + + context '#destroy' do + let!(:comment) { create(:comment) } + + it 'should delete the post' do + delete :destroy, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit + + comment.reload + expect(comment.destroyed?).to be(true) + end + + it 'should redirect to the post' do + delete :destroy, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit + + expect(response).to redirect_to(subcreddit_post_path(assigns(:subcreddit), + assigns(:post))) + end + + it 'should send a notice flash message' do + delete :destroy, + id: comment, + post_id: comment.post, + subcreddit_id: comment.post.subcreddit + expect(flash[:notice]).to be_present + end + end +end diff --git a/spec/factories/comment_factory.rb b/spec/factories/comment_factory.rb new file mode 100644 index 0000000..d9d4e16 --- /dev/null +++ b/spec/factories/comment_factory.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :comment do + user + post + content { Faker::Lorem.paragraph } + end +end diff --git a/spec/factories/user_factory.rb b/spec/factories/user_factory.rb index ee760d9..7d4c38a 100644 --- a/spec/factories/user_factory.rb +++ b/spec/factories/user_factory.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :user do - username { Faker::Internet.user_name } + sequence(:username) { |n| Faker::Internet.user_name + "#{n}" } password { Faker::Internet.password(8, 50) } email { Faker::Internet.email } end diff --git a/spec/features/comments/edit_comment_spec.rb b/spec/features/comments/edit_comment_spec.rb new file mode 100644 index 0000000..2dfd64f --- /dev/null +++ b/spec/features/comments/edit_comment_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe 'Edit Comment', type: :feature do + let!(:user) { create(:user) } + let!(:post) { create(:post) } + let!(:comment) { create(:comment, post: post, user: user) } + + context 'when signed in' do + let(:content) { 'Some different data' } + before(:each) { signin(user: user) } + + context 'with valid data' do + before(:each) do + visit edit_subcreddit_post_comment_path(post.subcreddit, post, comment) + + fill_in :comment_content, with: content + + click_button 'Update Comment' + end + + it 'should notify that the comment was edited' do + expect(page).to have_content('updated') + end + + it 'should update the comment' do + expect(page).to have_content(content) + end + end + end +end diff --git a/spec/features/comments/new_comment_spec.rb b/spec/features/comments/new_comment_spec.rb new file mode 100644 index 0000000..5b1e46d --- /dev/null +++ b/spec/features/comments/new_comment_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +describe 'New Comment', type: :feature do + let!(:post) { create(:post) } + let(:comment) { build(:comment) } + + context 'when signed in' do + let(:user) { create(:user) } + + before(:each) { signin(user: user) } + + context 'with valid data' do + before(:each) do + visit subcreddit_post_path(post.subcreddit, post) + + fill_in :comment_content, with: comment.content + + click_button 'Create Comment' + end + + it 'should notify that a new content was created' do + expect(page).to have_content('saved') + end + + it 'should display the new comment' do + expect(page).to have_content(comment.content) + end + + context 'when nesting comment' do + let!(:comment) { create(:comment, post: post) } + + it 'should display a nested comment' do + visit subcreddit_post_comment_path(post.subcreddit, post, comment) + + fill_in :comment_content, with: comment.content + click_button 'Create Comment' + + expect(page).to have_css('div.nested_comments') + end + end + end + + context 'with invalid data' do + before(:each) do + visit subcreddit_post_path(post.subcreddit, post) + + fill_in :comment_content, with: '' + + click_button 'Create Comment' + end + + it 'should display errors' do + expect(page).to have_content('could not') + end + end + end +end diff --git a/spec/features/posts/edit_post_spec.rb b/spec/features/posts/edit_post_spec.rb index c0e3cf2..0038cde 100644 --- a/spec/features/posts/edit_post_spec.rb +++ b/spec/features/posts/edit_post_spec.rb @@ -24,8 +24,9 @@ describe 'Edit Post', type: :feature do expect(page).to have_content('updated') end - it 'should show the post' do + it 'should show the updated post' do expect(page).to have_content(new_post.title) + expect(page).to have_content(new_post.content) end end end diff --git a/spec/features/posts/lists_posts_spec.rb b/spec/features/posts/lists_posts_spec.rb index 9fa1eb9..ef20dd6 100644 --- a/spec/features/posts/lists_posts_spec.rb +++ b/spec/features/posts/lists_posts_spec.rb @@ -9,7 +9,7 @@ describe 'List Posts', type: :feature do posts.each do |post| expect(page) - .to have_link(post.title, subcreddit_post_path(post, post.subcreddit)) + .to have_link(post.title, subcreddit_post_path(post.subcreddit, post)) end end end diff --git a/spec/helpers/comments_helper_spec.rb b/spec/helpers/comments_helper_spec.rb new file mode 100644 index 0000000..494935e --- /dev/null +++ b/spec/helpers/comments_helper_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +describe CommentsHelper do + describe '#nested_comments' do + it 'renders the comment partial' + end +end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb new file mode 100644 index 0000000..2108f89 --- /dev/null +++ b/spec/models/comment_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +describe Comment, type: :model do + let(:comment) { build(:comment) } + + it { should belong_to(:user) } + it { should belong_to(:post).counter_cache(true) } + + it { should delegate_method(:username).to(:user).with_prefix } + + context 'with valid data' do + it 'should be valid' do + expect(comment).to be_valid + end + end + + context 'with invalid data' do + it 'should not be valid with blank content' do + comment.content = '' + + expect(comment).to be_invalid + end + end + + context 'when comment is deleted' do + before(:each) { comment.deleted_at = Time.now } + + context '#destroyed?' do + it 'should respond with true' do + expect(comment.destroyed?).to be(true) + end + end + + context '#content' do + it 'should return [deleted]' do + expect(comment.content).to eq('[deleted]') + end + end + end + + context 'when comment is not deleted' do + context '#destroyed?' do + it 'should respond with false' do + expect(comment.destroyed?).to be(false) + end + end + + context '#content' do + it 'should return comment content' do + expect(comment.content).to eq(comment.content) + end + end + end + + context '#destroy' do + it 'should set the deleted_at time appropriately' do + Timecop.freeze do + comment.destroy + expect(comment.deleted_at).to eq(Time.now) + end + end + end +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 43264cd..4c26448 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -5,9 +5,20 @@ describe Post, type: :model do it { should belong_to(:user) } it { should belong_to(:subcreddit) } + it { should have_many(:comments) } it { should delegate_method(:username).to(:user).with_prefix } + context 'when adding a comment' do + let(:post) { create(:post) } + + it 'should update the cache_counter for comments' do + expect do + create(:comment, post: post) + end.to change { post.comments_count }.by(1) + end + end + context 'with valid data' do it 'should be valid' do expect(post).to be_valid @@ -53,4 +64,24 @@ describe Post, type: :model do expect(post.to_param).to eq("#{post.id}-#{post.title.parameterize}") end end + + context '#comments?' do + let(:post) { create(:post) } + + context 'with comments' do + before(:each) do + create(:comment, post: post) + end + + it 'should respond with true' do + expect(post.comments?).to be(true) + end + end + + context 'without comments' do + it 'should respond with false' do + expect(post.comments?).to be(false) + end + end + end end