From 882bd294f9be3bad3c35f947e646ff91e8758537 Mon Sep 17 00:00:00 2001 From: Julia Nguyen Date: Sun, 14 Oct 2018 01:23:39 -0700 Subject: [PATCH] [#966] Rewriting comments in React and refactoring comments services (#1143) - Delete all jQuery code related to comments - Rewrite frontend in React - Refactor services for comments - comments_helper.rb: logic for the comments array that gets passed as a prop to the Comments React widget - comment_form_helper.rb: form props for form used in Comments React widget - comments_viewers_service.rb (previously comments_visibility.rb): service for determining which comments to display to users/ability to delete - comments_controller.rb: moved all comment create/delete logic from other controllers into one - Update tests, write more tests (a lot of which were missing) - Only display commenting if moment/strategy is published --- app/assets/javascripts/add_comment.js | 88 ----- app/assets/javascripts/application.js | 12 - app/assets/javascripts/delete_comment.js | 30 -- .../stylesheets/application/comments.scss | 72 ---- .../stylesheets/application/strategy.scss | 9 - app/controllers/application_controller.rb | 7 +- app/controllers/comment_controller.rb | 59 ++++ app/controllers/meetings_controller.rb | 46 +-- app/controllers/moments_controller.rb | 41 --- app/controllers/secret_shares_controller.rb | 2 +- app/controllers/strategies_controller.rb | 55 +-- app/helpers/comment_form_helper.rb | 104 ++++++ app/helpers/comments_helper.rb | 118 ++----- app/helpers/medication_refill_helper.rb | 3 +- app/helpers/notifications_helper.rb | 2 +- app/models/comment.rb | 12 +- app/models/meeting.rb | 4 + app/models/moment.rb | 5 + app/models/strategy.rb | 4 + app/services/comment_viewers_service.rb | 84 +++++ app/services/comment_visibility.rb | 61 ---- app/views/meetings/show.html.erb | 8 +- app/views/moments/show.html.erb | 6 +- app/views/shared/_comments.html.erb | 92 +---- app/views/strategies/show.html.erb | 5 +- .../Form/DynamicForm.jsx} | 24 +- client/app/components/Form/index.jsx | 7 +- client/app/components/Story/StoryActions.jsx | 61 ++-- client/app/libs/testHelper.js | 1 + client/app/startup/registration.js | 2 + client/app/widgets/Comments/Comments.scss | 26 ++ .../Comments/__tests__/Comments.spec.jsx | 122 +++++++ client/app/widgets/Comments/index.jsx | 132 ++++++++ client/app/widgets/QuickCreate/index.jsx | 40 ++- config/routes.rb | 89 +---- spec/controllers/comment_controller_spec.rb | 319 ++++++++++++++++++ spec/controllers/meetings_controller_spec.rb | 111 +----- spec/controllers/moments_controller_spec.rb | 103 ------ .../secret_shares_controller_spec.rb | 1 + .../controllers/strategies_controller_spec.rb | 89 ----- .../user_creates_a_draft_moment_spec.rb | 3 +- .../user_creates_a_draft_strategy_spec.rb | 3 +- .../user_creates_a_published_moment_spec.rb | 2 +- .../user_creates_a_published_strategy_spec.rb | 2 +- spec/helpers/comment_form_helper_spec.rb | 228 +++++++++++++ spec/helpers/comments_helper_spec.rb | 191 ++++++----- spec/models/comment_spec.rb | 52 +++ spec/services/comment_viewers_service_spec.rb | 311 +++++++++++++++++ spec/services/comment_visibility_spec.rb | 125 ------- 49 files changed, 1739 insertions(+), 1234 deletions(-) delete mode 100644 app/assets/javascripts/add_comment.js delete mode 100644 app/assets/javascripts/delete_comment.js delete mode 100644 app/assets/stylesheets/application/comments.scss delete mode 100644 app/assets/stylesheets/application/strategy.scss create mode 100644 app/controllers/comment_controller.rb create mode 100644 app/helpers/comment_form_helper.rb create mode 100644 app/services/comment_viewers_service.rb delete mode 100644 app/services/comment_visibility.rb rename client/app/{widgets/QuickCreate/QuickCreateForm.jsx => components/Form/DynamicForm.jsx} (74%) create mode 100644 client/app/widgets/Comments/Comments.scss create mode 100644 client/app/widgets/Comments/__tests__/Comments.spec.jsx create mode 100644 client/app/widgets/Comments/index.jsx create mode 100644 spec/controllers/comment_controller_spec.rb create mode 100644 spec/helpers/comment_form_helper_spec.rb create mode 100644 spec/services/comment_viewers_service_spec.rb delete mode 100644 spec/services/comment_visibility_spec.rb diff --git a/app/assets/javascripts/add_comment.js b/app/assets/javascripts/add_comment.js deleted file mode 100644 index a8d148beb3..0000000000 --- a/app/assets/javascripts/add_comment.js +++ /dev/null @@ -1,88 +0,0 @@ -var onReadyAddComment = function() { - if (isShow(['moments', 'strategies', 'meetings'])) { - $(document).on('click', '#add_comment_button', function(event) { - event.preventDefault(); - - if (!$(this).prop('disabled')) { - $(this).prop('disabled', true); - $('#comment_comment').prop('disabled', true); - $(this).val(I18n.t('comment.posting')); - - var url; - if (isShow(['moments'])) { - url = '/moments/comment'; - } else if (isShow(['strategies'])) { - url = '/strategies/comment'; - } else { - url = '/meetings/comment'; - } - - var viewers; - if ($('#comment_viewers').length) { - viewers = $('#comment_viewers').val(); - } - - var data = { - commentable_type: $('#comment_commentable_type').val(), - commentable_id:$('#comment_commentable_id').val(), - comment_by: $('#comment_comment_by').val(), - comment: $('#comment_comment').val(), - visibility: $('#comment_visibility').val(), - viewers: viewers - }; - - $.ajax({ - dataType: 'json', - url: url, - type: 'POST', - data: data, - success: function(json) { - if (json !== undefined) { - $('#add_comment_button').prop('disabled', false); - $('#comment_comment').prop('disabled', false); - $('#comment_comment').val(''); - $('#add_comment_button').val(I18n.t('comment.singular')); - - if (!json.no_save) { - var commentid = 'comment_' + json.commentid; - var profile_picture = json.profile_picture; - var comment_info = json.comment_info; - var comment_text = json.comment_text; - var visibility = json.visibility; - var delete_comment = json.delete_comment; - - var newComment = '
'; - newComment += '
'; - newComment += '
'; - newComment += comment_info; - newComment += '
'; - if (delete_comment !== null && delete_comment.length > 0) { - newComment += delete_comment; - } - newComment += '
'; - newComment += '
'; - newComment += comment_text; - newComment += '
'; - newComment += '
'; - if (visibility !== null && visibility.length > 0) { - newComment += visibility; - } - newComment += '
'; - newComment += '
'; - - $('#comments').prepend(newComment); - } - } - }, - error: function() { - $('#add_comment_button').prop('disabled', false); - $('#comment_comment').prop('disabled', false); - $('#add_comment_button').val(I18n.t('comment.singular')); - } - }); - } - }); - } -}; - -loadPage(onReadyAddComment); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 7832673c58..6c8d52eb1b 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -24,18 +24,6 @@ I18n.locale = Cookies.get("locale") || I18n.defaultLocale; -function isShow(forms) { - var result = false; - _.each(forms, function(form) { - if ($("body").hasClass(form + " show")) { - result = true; - return; - } - }); - - return result; -} - var onReadyApplication = function() { $.ajaxSetup({ headers: { diff --git a/app/assets/javascripts/delete_comment.js b/app/assets/javascripts/delete_comment.js deleted file mode 100644 index 5fdb9e5686..0000000000 --- a/app/assets/javascripts/delete_comment.js +++ /dev/null @@ -1,30 +0,0 @@ -var onReadyDeleteComment = function() { - if (isShow(['moments', 'strategies', 'meetings'])) { - $(document).on('click', '.delete_comment_button', function(event) { - event.preventDefault(); - - var commentid = $(this).attr('id').replace('delete_comment_', ''); - var comment = '#comment_' + commentid; - $(comment).remove(); - - if ($('.comment').length === 0) { - $('.actions').addClass('noMarginBottom'); - } else { - $('.comment').first().addClass('noMarginTop'); - } - - var url; - if ($('body').hasClass('moments show')) { - url = "/moments/delete_comment?commentid=" + commentid; - } else if ($('body').hasClass('strategies show')) { - url = "/strategies/delete_comment?commentid=" + commentid; - } else { - url = "/meetings/delete_comment?commentid=" + commentid; - } - - $.ajax(url); - }); - } -}; - -loadPage(onReadyDeleteComment); diff --git a/app/assets/stylesheets/application/comments.scss b/app/assets/stylesheets/application/comments.scss deleted file mode 100644 index 495815b169..0000000000 --- a/app/assets/stylesheets/application/comments.scss +++ /dev/null @@ -1,72 +0,0 @@ -// Place all the styles related to the Moments controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ - -/* Comments */ - -.comment { - margin-top: $size-40; - box-sizing: border-box; - padding: $size-20; - box-shadow: $size-0 $size-2 $size-10 $black-10; - border-radius: $size-4; - background: $white; - - .delete_comment { - padding-left: $size-20; - text-align: right; - } - - @media screen and (max-width: $medium) { - margin-top: $size-20; - padding: $size-10; - - .delete_comment { - padding-left: $size-10; - } - } - - @media screen and (max-width: $small) { - .delete_comment { - padding-left: $size-4; - } - } -} - -.comment_info { - color: $mulberry; - margin-bottom: $size-10; - @media screen and (max-width: $small) { - font-size: $size-16; - } -} - -.comment a:link, -.comment a:visited, -.comment a:hover { - color: $mulberry; - border-color: $mulberry; -} - -.delete_comment a:link, -.delete_comment a:visited, -.delete_comment a:hover { - color: $mulberry; - border: none; -} - -.comment_text { - word-wrap: break-word; - margin-bottom: $size-10; -} - -.comment_textarea { - width: 100%; - margin: $size-0 !important; -} - -@media screen and (max-width: $medium) { - #comment_visibility { - margin-bottom: $size-10; - } -} diff --git a/app/assets/stylesheets/application/strategy.scss b/app/assets/stylesheets/application/strategy.scss deleted file mode 100644 index e0b86a8fff..0000000000 --- a/app/assets/stylesheets/application/strategy.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Place all the styles related to the strategy controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ - -@media screen and (max-width: $medium) { - #strategy_comment { - margin: 0; - } -} diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index af81bdef89..a4d54adc65 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -15,6 +15,7 @@ class ApplicationController < ActionController::Base include ActionView::Helpers::DateHelper include ActionView::Helpers::TextHelper include CommentsHelper + include CommentFormHelper include TagsHelper include MomentsHelper @@ -78,7 +79,7 @@ def configure_for_accept_invitation helper_method :avatar_url, :viewer_of?, :are_allies?, :get_uid, :most_focus, :tag_usage, :can_notify, :if_not_signed_in, - :generate_comment, :moments_stats + :moments_stats def if_not_signed_in return if user_signed_in? @@ -169,10 +170,6 @@ def redirect_to_path(path) end end - def respond_not_saved - respond_with_json(no_save: true) - end - def respond_with_json(reponse) respond_to do |format| format.html { render json: reponse } diff --git a/app/controllers/comment_controller.rb b/app/controllers/comment_controller.rb new file mode 100644 index 0000000000..0ec65ea4cc --- /dev/null +++ b/app/controllers/comment_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +class CommentController < ApplicationController + def create + response = { + comment: generate_comments( + Comment.where(id: handle_create(params[:comment])) + ).first + } + render json: response, status: :ok + rescue ActiveRecord::RecordInvalid + render json: {}, status: :bad_request + end + + def delete + comment = Comment.where(id: params[:comment_id]).first + raise 'Comment does not exist' if comment.nil? + + handle_delete(comment) + render json: { id: comment.id }, status: :ok + rescue TypeError, RuntimeError + render json: {}, status: :bad_request + end + + private + + def remove_meeting_notification!(comment_id) + CommentNotificationsService.remove( + comment_id: comment_id, + model_name: 'meeting' + ) + end + + def remove_meeting_notification(comment, meeting) + my_comment = comment.present? && (comment.comment_by == current_user.id) + return unless (my_comment && meeting.member?(current_user)) || + meeting.led_by?(current_user) + + remove_meeting_notification!(comment.id) + end + + def handle_create(comment_params) + comment = Comment.create_from!(comment_params) + comment.notify_of_creation!(current_user) + comment.id + end + + def handle_delete(comment) + if %w[moment strategy].include?(comment.commentable_type) + CommentViewersService.deletable?(comment, current_user) + CommentNotificationsService.remove(comment_id: comment.id, + model_name: + comment.commentable_type) + elsif comment.commentable_type == 'meeting' + meeting_id = comment.commentable_id + meeting = Meeting.find_by(id: meeting_id) + remove_meeting_notification(comment, meeting) + end + end +end diff --git a/app/controllers/meetings_controller.rb b/app/controllers/meetings_controller.rb index dc03b6ebce..431214e6ff 100644 --- a/app/controllers/meetings_controller.rb +++ b/app/controllers/meetings_controller.rb @@ -1,41 +1,17 @@ # frozen_string_literal: true -# rubocop:disable ClassLength class MeetingsController < ApplicationController - include CommentsHelper - before_action :set_meeting, only: %i[show edit update destroy] # GET /meetings/1 def show @meeting = Meeting.friendly.find(params[:id]) - @is_member = @meeting.member?(current_user) - @is_leader = @meeting.led_by?(current_user) - - if @is_member - @no_hide_page = true - @comment = Comment.new - @comments = @meeting.comments + if @meeting.member?(current_user) + @comments = generate_comments(@meeting.comments.order(created_at: :desc)) elsif !@meeting.group.member?(current_user) redirect_to_path(groups_path) end end - def comment - params[:visibility] = 'all' - comment_for('meeting') - end - - def delete_comment - comment = Comment.find_by(id: params[:commentid]) - - if comment.present? - meeting_id = comment.commentable_id - meeting = Meeting.find_by(id: meeting_id) - remove_notification(comment, meeting) - end - head :ok - end - # GET /meetings/new def new @group = Group.find_by(id: params[:group_id]) @@ -147,22 +123,4 @@ def send_notification(meeting, members, type) members: members ) end - - def remove_notification! - CommentNotificationsService.remove( - comment_id: params[:commentid], - model_name: 'meeting' - ) - end - - def remove_notification(comment, meeting) - remove_notification! if (my_comment?(comment) && \ - meeting.member?(current_user)) || \ - meeting.led_by?(current_user) - end - - def my_comment?(comment) - comment.present? && (comment.comment_by == current_user.id) - end end -# rubocop:enable ClassLength diff --git a/app/controllers/moments_controller.rb b/app/controllers/moments_controller.rb index d961ed04a4..40925b2aca 100644 --- a/app/controllers/moments_controller.rb +++ b/app/controllers/moments_controller.rb @@ -3,7 +3,6 @@ # rubocop:disable ClassLength class MomentsController < ApplicationController include CollectionPageSetup - include CommentsHelper include Shared before_action :set_moment, only: %i[show edit update destroy] @@ -14,12 +13,10 @@ class MomentsController < ApplicationController def index if current_user @user_logged_in = true - period = 'day' # +1 day buffer to ensure we include today as well end_date = Date.current + 1.day start_date = get_start_by_period(period, end_date) - @react_moments = Moment.where(user: current_user) .group_by_period(period, :created_at, @@ -27,7 +24,6 @@ def index else @user_logged_in = false end - page_collection('@moments', 'moment') end # rubocop:enable MethodLength @@ -38,39 +34,6 @@ def show show_with_comments(@moment) end - def comment - comment_for('moment') - end - - # rubocop:disable MethodLength - def delete_comment - comment_exists = Comment.where(id: params[:commentid]).exists? - is_my_comment = Comment.where( - id: params[:commentid], - comment_by: current_user.id - ).exists? - - if comment_exists - momentid = Comment.where(id: params[:commentid]).first.commentable_id - is_my_moment = Moment.where( - id: momentid, - user_id: current_user.id - ).exists? - is_a_viewer = viewer_of?(Moment.where(id: momentid).first.viewers) - else - is_my_moment = false - is_a_viewer = false - end - - if comment_exists && ((is_my_comment && is_a_viewer) || is_my_moment) - CommentNotificationsService.remove(comment_id: params[:commentid], - model_name: 'moment') - end - - head :ok - end - # rubocop:enable MethodLength - # GET /moments/new def new @moment = Moment.new @@ -140,13 +103,10 @@ def moment_params def set_association_variables! @viewers = current_user.allies_by_status(:accepted) - @categories = Category.where(user: current_user).order(created_at: :desc) @category = Category.new - @moods = Mood.where(user: current_user).order(created_at: :desc) @mood = Mood.new - @strategies = associated_strategies @strategy = Strategy.new end @@ -154,7 +114,6 @@ def set_association_variables! def associated_strategies # current_user's strategies and all viewable strategies from allies strategy_ids = current_user.strategies.pluck(:id) - @viewers.each do |ally| ally.strategies.each do |strategy| strategy_ids << strategy.id if strategy.viewer?(current_user) diff --git a/app/controllers/secret_shares_controller.rb b/app/controllers/secret_shares_controller.rb index cac845e7c1..75252e0468 100644 --- a/app/controllers/secret_shares_controller.rb +++ b/app/controllers/secret_shares_controller.rb @@ -10,7 +10,7 @@ def create secret_share_identifier: SecureRandom.uuid, secret_share_expires_at: 1.day.from_now ) - redirect_to moment_path(moment) + redirect_to moment_path(moment, anchor: 'secretShare') end def show diff --git a/app/controllers/strategies_controller.rb b/app/controllers/strategies_controller.rb index 9a573287dd..5d4fa2c891 100644 --- a/app/controllers/strategies_controller.rb +++ b/app/controllers/strategies_controller.rb @@ -3,7 +3,6 @@ # rubocop:disable ClassLength class StrategiesController < ApplicationController include CollectionPageSetup - include CommentsHelper include ReminderHelper include Shared @@ -21,53 +20,15 @@ def show show_with_comments(@strategy) end - def comment - comment_for('strategy') - end - - # rubocop:disable MethodLength - def delete_comment - comment_exists = Comment.where(id: params[:commentid]).exists? - is_my_comment = Comment.where( - id: params[:commentid], - comment_by: current_user.id - ).exists? - - if comment_exists - strategyid = Comment.where(id: params[:commentid]).first.commentable_id - is_my_strategy = Strategy.where( - id: strategyid, - user_id: current_user.id - ).exists? - else - is_my_strategy = false - end - - if comment_exists && (is_my_comment || is_my_strategy) - CommentNotificationsService.remove(comment_id: params[:commentid], - model_name: 'strategy') - end - - head :ok - end - # rubocop:enable MethodLength - - # rubocop:disable MethodLength def quick_create - # Assumme all viewers and comments allowed + # Assume all viewers and comments allowed viewers = [] current_user.allies_by_status(:accepted).each do |item| viewers.push(item.id) end - strategy = Strategy.new(user_id: current_user.id, - name: params[:strategy][:name], - description: params[:strategy][:description], - category: params[:strategy][:category], - published_at: Time.zone.now, - comment: true, viewers: viewers) + strategy = Strategy.new(quick_create_params(viewers)) shared_quick_create(strategy) end - # rubocop:enable MethodLength # GET /strategies/new def new @@ -183,5 +144,17 @@ def empty_array_for(*symbols) @strategy[symbol] = [] if strategy_params[symbol].nil? end end + + def quick_create_params(viewers) + { + user_id: current_user.id, + name: params[:strategy][:name], + description: params[:strategy][:description], + category: params[:strategy][:category], + published_at: Time.zone.now, + comment: true, + viewers: viewers + } + end end # rubocop:enable ClassLength diff --git a/app/helpers/comment_form_helper.rb b/app/helpers/comment_form_helper.rb new file mode 100644 index 0000000000..4e09bfeb2b --- /dev/null +++ b/app/helpers/comment_form_helper.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module CommentFormHelper + include FormHelper + + def comment_form_props(commentable, commentable_type) + inputs = [ + basic_props('commentable_type', 'hidden', commentable_type), + basic_props('comment_by', 'hidden', current_user.id), + basic_props('commentable_id', 'hidden', commentable.id), + basic_props('comment', 'textarea') + ].concat(visibility_or_viewers_input(commentable, commentable_type)) + quick_create_form_props(inputs, comment_index_path) + end + + private + + def basic_props_overrides(props, input_type) + props = props.merge(dark: true) if input_type != 'hidden' + if input_type == 'textarea' + props = props.merge(required: true, label: t('comment.singular')) + end + props + end + + def basic_props(field, input_type, value = nil) + props = { + id: "comment_#{field}", + name: "comment[#{field}]", + type: input_type, + value: value + } + basic_props_overrides(props, input_type) + end + + def comment_visibility_option(value, label) + { + id: "comment_visibility_#{value}", + label: label, + value: value + } + end + + def comment_visibility_options(owner) + [ + comment_visibility_option( + 'all', + t('shared.comments.share_everyone') + ), + comment_visibility_option( + 'private', + t('shared.comments.share_with', name: owner.name) + ) + ] + end + + def visibility_input_not_owner(owner) + { + options: comment_visibility_options(owner) + }.merge(basic_props('visibility', 'select', 'all')) + end + + def comment_viewers_option(field, label, value = nil) + { + id: "comment_viewers_#{field}", + label: label, + value: value + } + end + + def viewers_options(viewers) + options = [] + viewers.each do |viewer_id| + label = t('shared.comments.share_with', name: User.find(viewer_id).name) + options.push(comment_viewers_option(viewer_id, label, viewer_id)) + end + options + end + + def viewers_input_data_viewers(viewers) + { + options: [ + comment_viewers_option( + 'everyone', t('shared.comments.share_everyone'), '' + ) + ].concat(viewers_options(viewers)) + }.merge(basic_props('viewers', 'select', '')) + end + + def input_for_moment_or_strategy(commentable) + owner = User.find(commentable.user_id) + return [visibility_input_not_owner(owner)] if owner.id != current_user.id + return [] unless commentable.viewers.present? && + commentable.viewers.any? + + [viewers_input_data_viewers(commentable.viewers)] + end + + def visibility_or_viewers_input(commentable, commentable_type) + return [] unless %w[moment strategy].include?(commentable_type) + + input_for_moment_or_strategy(commentable) + end +end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 595a9ddc54..de7464d4a0 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -1,105 +1,57 @@ # frozen_string_literal: true module CommentsHelper - # rubocop:disable MethodLength - def generate_comment(data, data_type) - profile = User.find(data.comment_by) - comment_info = link_to(profile.name, profile_index_path(uid: profile.uid)) - if current_user != profile && !current_user.mutual_allies?(profile) - comment_info += " #{t('shared.comments.not_allies')}" - end - comment_info += " - #{TimeAgo.formatted_ago(data.created_at)}" - comment_text = sanitize(data.comment) - if data_type == 'moment' - visibility = CommentVisibility.build(data, - Moment.find(data.commentable_id), - current_user) - elsif data_type == 'strategy' - visibility = CommentVisibility.build(data, - Strategy.find(data.commentable_id), - current_user) - end - if comment_deletable?(data, data_type) - delete_comment = '
' - delete_comment += link_to sanitize(''), - '', - id: "delete_comment_#{data.id}", - class: 'delete_comment_button' - delete_comment += '
' + def generate_comments(comments) + result_comments = [] + comments.each do |comment| + next unless CommentViewersService.viewable?(comment, current_user) + + user = User.find(comment.comment_by) + result_comments.push(comment_hash(comment, user)) end - { - commentid: data.id, - comment_info: comment_info, - comment_text: comment_text, - visibility: visibility, - delete_comment: delete_comment, - no_save: false - } + result_comments end - # rubocop:enable MethodLength def show_with_comments(subject) model_name = record_model_name(subject) - if current_user.id != subject.user_id && hide_page?(subject) - path = send("#{model_name.pluralize}_path") - return redirect_to_path(path) + return redirect_to_path(send("#{model_name.pluralize}_path")) end - set_show_with_comments_variables(subject, model_name) - end - - def comment_for(model_name) - @comment = Comment.create_from!(params) - @comment.notify_of_creation!(current_user) + @page_author = page_author(subject) + return unless subject.comments - respond_with_json( - generate_comment(@comment, model_name) - ) - rescue ActiveRecord::RecordInvalid - respond_not_saved + @comments = generate_comments(subject.comments.order(created_at: :desc)) end private - # rubocop:disable MethodLength - def set_show_with_comments_variables(subject, model_name) - @page_author = if current_user.id != subject.user_id - User.find(subject.user_id) - else - User.find(current_user.id) - end - @no_hide_page = true - # rubocop:disable GuardClause - if subject.comment - @comment = Comment.new - @comments = Comment.where( - commentable_id: subject.id, - commentable_type: model_name - ).order(created_at: :desc) - end - # rubocop:enable GuardClause + def created_at(value) + t('created', created_at: TimeAgo.formatted_ago(value)) end - # rubocop:enable MethodLength - def comment_deletable?(data, data_type) - data.comment_by == current_user.id || - user_created_data?(data.commentable_id, data_type) + def comment_hash(comment, user) + { + id: comment.id, + commentByUid: user.uid, + commentByName: user.name, + commentByAvatar: user.avatar.url, + comment: sanitize(comment.comment), + viewers: CommentViewersService.viewers(comment, current_user), + createdAt: created_at(comment.created_at), + deleteAction: delete_action(comment) + } end - # rubocop:disable MethodLength - def user_created_data?(id, data_type) - case data_type - when 'moment' - Moment.where(id: id, user_id: current_user.id).exists? - when 'strategy' - Strategy.where(id: id, user_id: current_user.id).exists? - when 'meeting' - MeetingMember.where(meeting_id: id, leader: true, - user_id: current_user.id).exists? - else - false - end + def page_author(subject) + return User.find(current_user.id) unless current_user.id != subject.user_id + + User.find(subject.user_id) + end + + def delete_action(comment) + return unless CommentViewersService.deletable?(comment, current_user) + + delete_comment_index_path(comment_id: comment.id) end - # rubocop:enable MethodLength end diff --git a/app/helpers/medication_refill_helper.rb b/app/helpers/medication_refill_helper.rb index 5e6ff8cf9c..e50656e063 100644 --- a/app/helpers/medication_refill_helper.rb +++ b/app/helpers/medication_refill_helper.rb @@ -2,10 +2,11 @@ module MedicationRefillHelper include CalendarHelper - # Save refill date to Google calendar + def save_refill_to_google_calendar(medication) return true unless current_user.google_oauth2_enabled? && new_cal_refill_reminder_needed?(medication) + args = calendar_uploader_params(medication) CalendarUploader.new(args).upload_event rescue Google::Apis::ClientError diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 7f2bc68df6..7e629b00b9 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -7,7 +7,7 @@ def comment_link(uniqueid, data) notification = t( "notifications.comment.#{i18n_key}", name: data[:user], - comment: data[:comment], + comment: strip_tags(data[:comment]), typename: data[:typename] ) notification_link(uniqueid, comment[:path], notification) diff --git a/app/models/comment.rb b/app/models/comment.rb index 165b4e08f4..8d3ceb78a4 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -20,7 +20,8 @@ class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true validates :comment, length: { minimum: 0, maximum: 1000 }, presence: true - validates :commentable_type, :commentable_id, :comment_by, presence: true + validates :commentable_type, inclusion: %w[moment strategy meeting] + validates :commentable_id, :comment_by, presence: true validates :visibility, inclusion: %w[all private] before_save :array_data @@ -34,7 +35,7 @@ def array_data class << self def create_from!(params) viewers = params[:viewers].blank? ? [] : [params[:viewers].to_i] - + params[:visibility] = 'all' if params[:visibility].blank? Comment.create!( commentable_type: params[:commentable_type], commentable_id: params[:commentable_id], @@ -44,6 +45,13 @@ def create_from!(params) viewers: viewers ) end + + def comments_from(data) + Comment.where( + commentable_id: data.id, + commentable_type: data.class.name.downcase + ) + end end # Notify commentable_id user that they have a new comment diff --git a/app/models/meeting.rb b/app/models/meeting.rb index 63a607aa95..d67549d8db 100644 --- a/app/models/meeting.rb +++ b/app/models/meeting.rb @@ -36,4 +36,8 @@ def member?(user) def led_by?(user) leaders.include? user end + + def comments + Comment.comments_from(self) + end end diff --git a/app/models/moment.rb b/app/models/moment.rb index af57f58606..c293842e01 100644 --- a/app/models/moment.rb +++ b/app/models/moment.rb @@ -37,6 +37,7 @@ class Moment < ApplicationRecord before_save :strategy_array_data belongs_to :user + has_many :comments, as: :commentable validates :comment, inclusion: [true, false] @@ -83,4 +84,8 @@ def shared? secret_share_identifier? # && Time.zone.now < secret_share_expires_at TODO: Turn off temporarily end + + def comments + Comment.comments_from(self) + end end diff --git a/app/models/strategy.rb b/app/models/strategy.rb index 5186585cce..2eafec9ae1 100644 --- a/app/models/strategy.rb +++ b/app/models/strategy.rb @@ -55,4 +55,8 @@ def published? def self.link '/strategies' end + + def comments + Comment.comments_from(self) + end end diff --git a/app/services/comment_viewers_service.rb b/app/services/comment_viewers_service.rb new file mode 100644 index 0000000000..443231fec7 --- /dev/null +++ b/app/services/comment_viewers_service.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +class CommentViewersService + attr_reader :comment, :owner, :current_user, :commentable_viewers + + def self.viewers(comment, current_user) + new(comment, current_user).viewers + end + + def self.viewable?(comment, current_user) + new(comment, current_user).viewable? + end + + def self.deletable?(comment, current_user) + new(comment, current_user).deletable? + end + + def initialize(comment, current_user) + commentable = get_commentable(comment) + @comment = comment + @owner = commentable[:user_id] && User.find(commentable[:user_id]) + @commentable_viewers = + unless commentable.nil? + commentable[:viewers] || commentable.members&.pluck(:id) + end + @current_user = current_user + end + + def viewers + return unless @comment.visibility == 'private' && viewable? + + I18n.t('shared.comments.visible_only_between_you_and', + name: other_person.name) + end + + def viewable? + current_user_comment? || commentable_owner? || + viewer? + end + + def deletable? + current_user_comment? || commentable_owner? + end + + private + + def other_person + return @owner unless commentable_owner? + + User.find_by(id: @comment.viewers.first) || + User.find_by(id: @comment.comment_by) + end + + def commentable_owner? + if @comment.commentable_type == 'meeting' + meeting = Meeting.find_by(id: @comment.commentable_id) + return meeting&.led_by?(current_user) + end + + @owner.id == @current_user.id + end + + def current_user_comment? + @comment.comment_by == @current_user.id + end + + def comment_viewer? + @comment.viewers.present? && @comment.viewers.include?(@current_user.id) + end + + def commentable_viewer? + @comment.visibility == 'all' && + @commentable_viewers.include?(@current_user.id) + end + + def viewer? + comment_viewer? || commentable_viewer? + end + + def get_commentable(comment) + model = comment.commentable_type.classify.constantize + model.find(comment.commentable_id) + end +end diff --git a/app/services/comment_visibility.rb b/app/services/comment_visibility.rb deleted file mode 100644 index 8c40a2265d..0000000000 --- a/app/services/comment_visibility.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -class CommentVisibility - attr_reader :comment, :owner, :current_user - - def self.build(comment, commentable_id, current_user) - new(comment, commentable_id, current_user).build - end - - def initialize(comment, commentable_id, current_user) - @comment = comment - @owner = User.find(commentable_id.user_id) - @current_user = current_user - end - - def build - return unless should_show_visibility? - - I18n.t('shared.comments.visible_only_between_you_and', - name: other_person.name) - end - - private - - def should_show_visibility? - @comment.visibility == 'private' && logged_in_user_can_view_comment? - end - - def other_person - if logged_in_as_owner? - if (viewer = User.where(id: @comment.viewers[0]).first) - # you are logged in as owner, you made the comment, - # and it is visible to a viewer - viewer - else - # you are logged in as owner, and comment was made by somebody else - User.find(@comment.comment_by) - end - else - # you are logged in as comment maker, and it is visible to you and owner - @owner - end - end - - def logged_in_as_owner? - @owner.id == @current_user.id - end - - def logged_in_user_made_comment? - @comment.comment_by == @current_user.id - end - - def logged_in_user_is_viewer? - @comment.viewers.present? && @comment.viewers.include?(@current_user.id) - end - - def logged_in_user_can_view_comment? - logged_in_user_made_comment? || logged_in_as_owner? || - logged_in_user_is_viewer? - end -end diff --git a/app/views/meetings/show.html.erb b/app/views/meetings/show.html.erb index 8284c2b9c7..2b429aea06 100644 --- a/app/views/meetings/show.html.erb +++ b/app/views/meetings/show.html.erb @@ -32,6 +32,8 @@ <% end %> -
- <%= render partial: '/shared/comments', locals: { data: @meeting, comments: @comments, comment: @comment, no_hide_page: @no_hide_page, commentable_type: 'meeting', is_member: @is_member, is_leader: @is_leader } %> -
+<% if @meeting.member?(current_user) %> +
+ <%= render partial: '/shared/comments', locals: { commentable: @meeting } %> +
+<% end %> diff --git a/app/views/moments/show.html.erb b/app/views/moments/show.html.erb index d3045f2755..726d6f20da 100644 --- a/app/views/moments/show.html.erb +++ b/app/views/moments/show.html.erb @@ -45,7 +45,7 @@ <% if @moment.owned_by?(current_user) && @moment.shared? %>
-
<%= label_tag t('moments.secret_share.singular') %>
+
<%= label_tag t('moments.secret_share.singular') %>
/>
<% end %> @@ -81,6 +81,6 @@ <% end %> -<% if @comment.present? %> - <%= render partial: '/shared/comments', locals: { data: @moment, comments: @comments, comment: @comment, no_hide_page: @no_hide_page, commentable_type: 'moment' } %> +<% if user_signed_in? && @moment.published? && @moment.comment %> + <%= render partial: '/shared/comments', locals: { commentable: @moment } %> <% end %> diff --git a/app/views/shared/_comments.html.erb b/app/views/shared/_comments.html.erb index a6059446c6..60740a5fed 100644 --- a/app/views/shared/_comments.html.erb +++ b/app/views/shared/_comments.html.erb @@ -1,87 +1,7 @@ -<% if ( - ( - ( - local_assigns[:commentable_type] == 'moment' || - local_assigns[:commentable_type] == 'strategy' - ) && - local_assigns[:data].comment && - ( - current_user.id == local_assigns[:data].user_id || - local_assigns[:no_hide_page] - ) - ) || - ( - local_assigns[:commentable_type] == 'meeting' && - local_assigns[:is_member] +<%= react_component('Comments', props: { + comments: @comments, + formProps: comment_form_props( + local_assigns[:commentable], + local_assigns[:commentable].class.name.downcase ) -) %> -
- <%= form_for local_assigns[:comment], url: { action: "comment" }, html: { method: "post" } do |f| %> - <% if local_assigns[:comment].errors.any? %> -
- <%= t('common.form.error_explanation') %> -
- <% end %> - - <%= f.text_area :comment, class: 'comment_textarea' %> - - <%= f.hidden_field :commentable_type, value: local_assigns[:commentable_type] %> - <%= f.hidden_field :comment_by, value: current_user.id %> - <%= f.hidden_field :commentable_id, value: local_assigns[:data].id %> - -
- <% if (local_assigns[:commentable_type] == 'moment' || local_assigns[:commentable_type] == 'strategy') && local_assigns[:data].user_id != current_user.id %> - <%= f.select :visibility, [ - [t('shared.comments.share_everyone'), 'all'], - [t('shared.comments.share_with', name: User.where(id: local_assigns[:data].user_id).first.name), 'private'] - ] %> - <% else %> - <%= f.hidden_field :visibility, value: 'all' %> - <% if (local_assigns[:commentable_type] == 'moment' || local_assigns[:commentable_type] == 'strategy') && !local_assigns[:data].viewers.blank? && local_assigns[:data].viewers.length > 0 %> - <%= f.select :viewers, local_assigns[:data].viewers.collect { |v| - [ t('shared.comments.share_with', name: User.where(id: v).first.name), v ] - }, include_blank: t('shared.comments.share_everyone') %> - <% end %> - <% end %> -
- - <%= f.submit t('comment.singular'), class: 'smallerMarginTop buttonDarkS', id: 'add_comment_button' %> -
- <% end %> - -
- <% local_assigns[:comments].each do |comment| %> - <% if comment.visibility == 'all' || - ( - comment.visibility == 'private' && - User.where(id: comment.comment_by).exists? && - ( - comment.comment_by == current_user.id || - local_assigns[:is_member] || - current_user.id == local_assigns[:data].user_id || - ( - !comment.viewers.blank? && - comment.viewers.include?(current_user.id) - ) - ) - ) - %> - <% result = generate_comment(comment, comment.commentable_type) %> -
> -
-
- <%= raw result[:comment_info] %> -
- <%= raw result[:delete_comment] %> -
-
- <%= raw result[:comment_text] %> -
-
- <%= raw result[:visibility] %> -
-
- <% end %> - <% end %> -
-<% end %> +}) %> diff --git a/app/views/strategies/show.html.erb b/app/views/strategies/show.html.erb index 9b74aa8db0..e874a7f007 100644 --- a/app/views/strategies/show.html.erb +++ b/app/views/strategies/show.html.erb @@ -48,7 +48,8 @@ <% end %> - -<%= render partial: '/shared/comments', locals: { data: @strategy, comments: @comments, comment: @comment, no_hide_page: @no_hide_page, commentable_type: 'strategy' } %> +<% if @strategy.published? && @strategy.comment %> + <%= render partial: '/shared/comments', locals: { commentable: @strategy } %> +<% end %> <%= render partial: '/tag_usage/index', locals: { data: @strategy.id, data_type: 'strategy', user_id: @strategy.user_id } %> diff --git a/client/app/widgets/QuickCreate/QuickCreateForm.jsx b/client/app/components/Form/DynamicForm.jsx similarity index 74% rename from client/app/widgets/QuickCreate/QuickCreateForm.jsx rename to client/app/components/Form/DynamicForm.jsx index 513724e398..6df3b4de52 100644 --- a/client/app/widgets/QuickCreate/QuickCreateForm.jsx +++ b/client/app/components/Form/DynamicForm.jsx @@ -1,10 +1,10 @@ // @flow import React from 'react'; import axios from 'axios'; -import { Form } from '../../components/Form'; +import { Form } from './index'; export type Props = { - nameValue: string, + nameValue?: string, formProps: any, onCreate: Function, }; @@ -13,7 +13,7 @@ export type State = { formProps: any, }; -export class QuickCreateForm extends React.Component { +export class DynamicForm extends React.Component { myRef: any; constructor(props: Props) { @@ -24,13 +24,17 @@ export class QuickCreateForm extends React.Component { processFormProps = () => { const { nameValue, formProps } = this.props; const processedFormProps = Object.assign({}, formProps); - processedFormProps.inputs[0].value = nameValue; + if (nameValue) { + processedFormProps.inputs[0].value = nameValue; + } return processedFormProps; }; getParams = () => { const params = {}; - Array.from(this.myRef.querySelectorAll('input')).forEach((input: any) => { + const inputs = Array.from(this.myRef.querySelectorAll('input')); + const selects = Array.from(this.myRef.querySelectorAll('select')); + inputs.concat(selects).forEach((input: any) => { if (input.id !== 'submit') { // Assumes name is in model[column] format const indexOfFirstBracket = input.name.indexOf('['); @@ -51,13 +55,9 @@ export class QuickCreateForm extends React.Component { onSubmit = () => { const { formProps } = this.state; axios.post(formProps.action, this.getParams()).then((response: any) => { - const { data } = response; - if (data && data.success) { - const { onCreate } = this.props; - const { name, id, slug } = data; - if (onCreate) { - onCreate({ label: name, value: id, id: slug }); - } + const { onCreate } = this.props; + if (onCreate) { + onCreate(response); } }); }; diff --git a/client/app/components/Form/index.jsx b/client/app/components/Form/index.jsx index bf9d5d96e5..9fc095aabe 100644 --- a/client/app/components/Form/index.jsx +++ b/client/app/components/Form/index.jsx @@ -70,19 +70,18 @@ export class Form extends React.Component { } return newInput; }); + const { noFormTagSubmit } = this.props; if (hasErrors(newErrors) > 0) { e.preventDefault(); this.setState({ inputs: newInputs, errors: newErrors }); + } else if (noFormTagSubmit) { + noFormTagSubmit(); } }; handleNoFormTagSubmit = (e: SyntheticEvent) => { e.preventDefault(); this.onSubmit(e); - const { noFormTagSubmit } = this.props; - if (noFormTagSubmit) { - noFormTagSubmit(); - } }; displayInput = (input: MyInputProps) => { diff --git a/client/app/components/Story/StoryActions.jsx b/client/app/components/Story/StoryActions.jsx index 4bcc51c93a..39ab9edaa1 100644 --- a/client/app/components/Story/StoryActions.jsx +++ b/client/app/components/Story/StoryActions.jsx @@ -16,6 +16,7 @@ export type Action = { link: string, dataMethod?: string, dataConfirm?: string, + onClick?: Function, }; export type Actions = { @@ -71,36 +72,50 @@ const displayViewers = ( ); -const displayLink = ( - actions: Actions, - item: string, - hasStory: ?boolean, - dark: ?boolean, -) => { - const titleItem = item.charAt(0).toUpperCase() + item.slice(1); - const element = ( +const titleItem = (item: string) => item.charAt(0).toUpperCase() + item.slice(1); + +const tooltipElement = (item: string, actions: Actions, dark: ?boolean) => { + const { + link, dataMethod, dataConfirm, name, onClick, + } = actions[item]; + return ( ) => onClick(e, link) + : undefined + } > {classMap(dark)[item]} ); - return ( -
- -
- ); }; +const displayLink = ( + actions: Actions, + item: string, + hasStory: ?boolean, + dark: ?boolean, +) => ( +
+ +
+); + const displayItem = ( actions: Actions, item: string, diff --git a/client/app/libs/testHelper.js b/client/app/libs/testHelper.js index 551256960c..a756e2ea69 100644 --- a/client/app/libs/testHelper.js +++ b/client/app/libs/testHelper.js @@ -8,6 +8,7 @@ import { setup } from './i18n/setup'; Enzyme.configure({ adapter: new Adapter() }); window.alert = () => {}; window.location.reload = () => {}; +window.document.execCommand = () => {}; setup(); export { React, TestUtils }; diff --git a/client/app/startup/registration.js b/client/app/startup/registration.js index 09b0776cb2..56380b4ccf 100644 --- a/client/app/startup/registration.js +++ b/client/app/startup/registration.js @@ -27,6 +27,7 @@ import { Accordion } from '../components/Accordion'; import { Resources } from '../widgets/Resources'; import { Notifications } from '../widgets/Notifications'; import { ToggleLocale } from '../widgets/ToggleLocale'; +import { Comments } from '../widgets/Comments'; setup(); @@ -36,6 +37,7 @@ ReactOnRails.register({ Accordion, Chart, ChartControl, + Comments, Form, Header, HeaderProfile, diff --git a/client/app/widgets/Comments/Comments.scss b/client/app/widgets/Comments/Comments.scss new file mode 100644 index 0000000000..f300aaf49a --- /dev/null +++ b/client/app/widgets/Comments/Comments.scss @@ -0,0 +1,26 @@ +@import '../../styles/_global.scss'; + +.comments { + @include setMargin($size-40, $size-0, $size-0, $size-0); +} + +.comment { + @include fadeIn(1.5s); + @include gridItemBoxLight(); + + &:last-of-type { + @include setMargin($size-0, $size-0, $size-0, $size-0); + } + + &Info { + @include setMargin($size-20, $size-0, $size-0, $size-0); + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + &Content { + @include setMargin($size-0, $size-0, $size-30, $size-0); + } +} \ No newline at end of file diff --git a/client/app/widgets/Comments/__tests__/Comments.spec.jsx b/client/app/widgets/Comments/__tests__/Comments.spec.jsx new file mode 100644 index 0000000000..08ac26ab83 --- /dev/null +++ b/client/app/widgets/Comments/__tests__/Comments.spec.jsx @@ -0,0 +1,122 @@ +// @flow +import React from 'react'; +import axios from 'axios'; +import { mount } from 'enzyme'; +import { Comments } from '../index'; + +let axiosPostSpy; +let axiosDeleteSpy; + +const formProps = { + inputs: [ + { + id: 'comment_commentable_type', + name: 'comment[commentable_type]', + type: 'hidden', + value: 'moment', + }, + { + id: 'comment_comment_by', + name: 'comment[comment_by]', + type: 'hidden', + value: 1, + }, + { + id: 'comment_commentable_id', + name: 'comment[commentable_id]', + type: 'hidden', + value: 19, + }, + { + id: 'comment_comment', + name: 'comment[comment]', + type: 'textarea', + dark: true, + }, + { + options: [ + { + id: 'comment_visibility_all', + label: 'Share with everyone', + value: 'all', + }, + { + id: 'comment_visibility_private', + label: 'Share with Testy 2 only', + value: 'private', + }, + ], + id: 'comment_visibility', + name: 'comment[visibility]', + type: 'select', + value: 'all', + dark: true, + }, + { + id: 'submit', + type: 'submit', + value: 'Submit', + dark: true, + }, + ], + action: '/comment/create', + noFormTag: true, +}; + +const value = 'Hey'; + +const id = 1; + +const comment = { + comment: value, + commentByAvatar: null, + commentByName: 'Kind Human', + commentByUid: 'uid', + createdAt: 'Created less than a minute ago', + deleteAction: '/comment/delete?comment_id=1', + id, + viewers: 'Visible only between you and Lane Kim', +}; + +const component = ; + +describe('Comments', () => { + beforeEach(() => { + axiosPostSpy = jest.spyOn(axios, 'post').mockImplementation(() => Promise.resolve({ + data: { comment }, + })); + axiosDeleteSpy = jest.spyOn(axios, 'delete').mockImplementation(() => Promise.resolve({ + data: { id }, + })); + }); + + it('renders correctly', () => { + let wrapper = null; + expect(() => { + wrapper = mount(component); + }).not.toThrow(); + expect(wrapper).not.toBeNull(); + }); + + it('add and delete a comment', async () => { + const wrapper = mount(component); + expect(wrapper.find('.comment').exists()).toEqual(false); + wrapper.find('input[name="comment[comment]"]').simulate('change', { + currentTarget: { value }, + }); + wrapper.find('select#comment_visibility').prop('onChange')({ + currentTarget: { value: 'private' }, + }); + wrapper.find('input[type="submit"]').simulate('click'); + await axiosPostSpy(); + wrapper.update(); + expect(wrapper.find('.comment').exists()).toEqual(true); + wrapper + .find('.storyActionsDelete') + .find('a') + .simulate('click'); + await axiosDeleteSpy(); + wrapper.update(); + expect(wrapper.find('.comment').exists()).toEqual(false); + }); +}); diff --git a/client/app/widgets/Comments/index.jsx b/client/app/widgets/Comments/index.jsx new file mode 100644 index 0000000000..08c32d0057 --- /dev/null +++ b/client/app/widgets/Comments/index.jsx @@ -0,0 +1,132 @@ +// @flow +import React from 'react'; +import axios from 'axios'; +import renderHTML from 'react-render-html'; +import { StoryBy } from '../../components/Story/StoryBy'; +import { StoryDate } from '../../components/Story/StoryDate'; +import { StoryActions } from '../../components/Story/StoryActions'; +import { DynamicForm } from '../../components/Form/DynamicForm'; +import css from './Comments.scss'; +import { I18n } from '../../libs/i18n'; +import { Utils } from '../../utils'; + +type Comment = { + id: number, + commentByUid: string, + commentByName: string, + commentByAvatar?: string, + comment: any, + viewers?: string, + createdAt: string, + deleteAction?: string, +}; + +export type Props = { + comments?: Comment[], + formProps: any, +}; + +export type State = { + comments: (Comment | any)[], + key?: string, +}; + +export class Comments extends React.Component { + constructor(props: Props) { + super(props); + this.state = { comments: props.comments || [] }; + } + + onDeleteClick = (e: SyntheticEvent, action: string) => { + e.preventDefault(); + axios.delete(action).then((response: any) => { + const { data } = response; + if (data && data.id) { + this.setState((prevState: State) => { + const { comments } = prevState; + const newComments = comments.filter( + (comment: Comment) => comment.id !== parseInt(data.id, 10), + ); + return { comments: newComments }; + }); + } + }); + }; + + getActions = (viewers: ?string, deleteAction: ?string) => { + const actions = {}; + if (viewers) { + actions.viewers = viewers; + } + if (deleteAction) { + actions.delete = { + name: I18n.t('common.actions.delete'), + link: deleteAction, + dataConfirm: I18n.t('common.actions.confirm'), + onClick: this.onDeleteClick, + }; + } + return actions; + }; + + displayComment = (myComment: Comment) => { + const { + id, + commentByUid, + commentByName, + commentByAvatar, + comment, + viewers, + createdAt, + deleteAction, + } = myComment; + const author = {commentByName}; + return ( +
+
{renderHTML(comment)}
+ +
+ + +
+
+ ); + }; + + onCreate = (response: any) => { + const { data } = response; + if (data && data.comment) { + this.setState((prevState: State) => { + const { comments } = prevState; + return { + comments: [data.comment].concat(comments), + key: Utils.randomString(), + }; + }); + } + }; + + displayComments = () => { + const { comments } = this.state; + if (comments.length === 0) return null; + return ( +
+ {comments.map((comment: Comment) => this.displayComment(comment))} +
+ ); + }; + + render() { + const { formProps } = this.props; + const { key } = this.state; + return ( +
+ + {this.displayComments()} +
+ ); + } +} diff --git a/client/app/widgets/QuickCreate/index.jsx b/client/app/widgets/QuickCreate/index.jsx index 6d530c075f..db5c4cd135 100644 --- a/client/app/widgets/QuickCreate/index.jsx +++ b/client/app/widgets/QuickCreate/index.jsx @@ -5,7 +5,7 @@ import { Input } from '../../components/Input'; import type { Checkbox } from '../../components/Input'; import { Utils } from '../../utils'; import css from './QuickCreate.scss'; -import { QuickCreateForm } from './QuickCreateForm'; +import { DynamicForm } from '../../components/Form/DynamicForm'; // value - e.g. category.id // label - e.g. category.name @@ -35,7 +35,7 @@ const alpha = (a: string, b: string) => { return 0; }; -const sortAlpha = (checkboxes: Checkbox[]) => +const sortAlpha = (checkboxes: Checkbox[]): Checkbox[] => // eslint-disable-next-line implicit-arrow-linebreak checkboxes.sort((a: Checkbox, b: Checkbox) => alpha(a.label, b.label)); @@ -71,30 +71,36 @@ export class QuickCreate extends React.Component { ).length; }; - onCreate = (checkbox: { label: string, value: number, id: string }) => { - const { label, value, id } = checkbox; - this.setState((prevState: State) => { - const { checkboxes } = prevState; - checkboxes.push({ - id, - label, - value, - checked: true, - }); - return { + addToCheckboxes = (data: { name: string, id: string, slug: string }) => { + const { checkboxes } = this.state; + const { name, id, slug } = data; + const newCheckboxes = checkboxes.slice(0); + newCheckboxes.push({ + id: slug, + label: name, + value: id, + checked: true, + }); + return sortAlpha(newCheckboxes); + }; + + onCreate = (response: any) => { + const { data } = response; + if (data && data.success) { + this.setState({ open: false, accordionOpen: true, modalKey: Utils.randomString(), tagKey: Utils.randomString(), - checkboxes: sortAlpha(checkboxes), - }; - }); + checkboxes: this.addToCheckboxes(data), + }); + } }; displayQuickCreateForm = (nameValue: string) => { const { formProps } = this.props; return ( - 'errors#not_found' get '/500' => 'errors#internal_server_error' - resources :allies, :except => [:show, :new, :create, :edit, :update, :destroy] do + resources :allies, except: [:show, :new, :create, :edit, :update, :destroy] do collection do post 'add' post 'remove' end end + resources :comment, only: [:create, :destroy] do + collection do + post 'create' + delete 'delete' + end + end + resources :medications resources :moods do @@ -28,21 +35,14 @@ end end - resources :moments do - collection do - post 'comment' - get 'delete_comment' - end - end + resources :moments resources :secret_shares, only: [:create, :show, :destroy] resources :strategies do collection do - post 'comment' post 'premade' post 'quick_create' - get 'delete_comment' end end @@ -58,20 +58,18 @@ collection do get 'join' get 'leave' - post 'comment' - get 'delete_comment' end end - resources :profile, :except => [:show, :new, :create, :edit, :update, :destroy] + resources :profile, except: [:show, :new, :create, :edit, :update, :destroy] - resources :search, :except => [:show, :new, :create, :edit, :update, :destroy] do + resources :search, except: [:show, :new, :create, :edit, :update, :destroy] do collection do get 'posts' end end - resources :notifications, :except => [:show, :new, :create, :edit, :update] do + resources :notifications, except: [:show, :new, :create, :edit, :update] do collection do delete 'clear' get 'fetch_notifications' @@ -89,10 +87,10 @@ match 'press', to: 'pages#press', via: :get match 'resources', to: 'pages#resources', via: :get - devise_for :users, :controllers => { :registrations => :registrations, - :omniauth_callbacks => 'omniauth_callbacks', - :invitations => 'users/invitations', - :sessions => :sessions } + devise_for :users, controllers: { registrations: :registrations, + omniauth_callbacks: 'omniauth_callbacks', + invitations: 'users/invitations', + sessions: :sessions } post 'pusher/auth' @@ -100,60 +98,5 @@ get locale.to_s => 'locales#set_initial_locale', defaults: { locale: locale.to_s } end - # The priority is based upon order of creation: first created -> highest priority. - # See how all your routes lay out with "rake routes". - - # You can have the root of your site routed with "root" - # root 'welcome#index' - - # Example of regular route: - # get 'products/:id' => 'catalog#view' - - # Example of named route that can be invoked with purchase_url(id: product.id) - # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase - - # Example resource route (maps HTTP verbs to controller actions automatically): - # resources :products - - # Example resource route with options: - # resources :products do - # member do - # get 'short' - # post 'toggle' - # end - # - # collection do - # get 'sold' - # end - # end - - # Example resource route with sub-resources: - # resources :products do - # resources :comments, :sales - # resource :seller - # end - - # Example resource route with more complex sub-resources: - # resources :products do - # resources :comments - # resources :sales do - # get 'recent', on: :collection - # end - # end - - # Example resource route with concerns: - # concern :toggleable do - # post 'toggle' - # end - # resources :posts, concerns: :toggleable - # resources :photos, concerns: :toggleable - - # Example resource route within a namespace: - # namespace :admin do - # # Directs /admin/products/* to Admin::ProductsController - # # (app/controllers/admin/products_controller.rb) - # resources :products - # end - root 'pages#home' end diff --git a/spec/controllers/comment_controller_spec.rb b/spec/controllers/comment_controller_spec.rb new file mode 100644 index 0000000000..17cb8d7986 --- /dev/null +++ b/spec/controllers/comment_controller_spec.rb @@ -0,0 +1,319 @@ +# frozen_string_literal: true + +describe CommentController do + let(:user) { create(:user) } + + describe 'POST create' do + let(:comment) { build(:comment, comment_by: user.id, commentable_type: commentable_type) } + + context 'from a moment' do + let(:commentable) { create(:moment, user: user) } + let(:commentable_type) { 'moment' } + let(:valid_comment_params) do + { comment: comment.attributes.merge(commentable_id: commentable.id, visibility: 'all') } + end + let(:invalid_comment_params) { { comment: comment.attributes } } + + context 'when the user is logged in' do + include_context :logged_in_user + + context 'when the comment is saved' do + it 'responds with an OK status' do + post :create, params: valid_comment_params + expect(response.status).to eq(200) + end + end + + context 'when the comment is not saved' do + it 'renders correct response' do + post :create, params: invalid_comment_params + expect(response.body).to eq({}.to_json) + end + end + end + + context 'when the user is not logged in' do + before { post :create } + it_behaves_like :with_no_logged_in_user + end + end + + context 'from a strategy' do + let(:commentable) { create(:strategy, user: user) } + let(:commentable_type) { 'strategy' } + let(:valid_comment_params) do + { comment: comment.attributes.merge(commentable_id: commentable.id, visibility: 'all') } + end + let(:invalid_comment_params) { { comment: comment.attributes } } + + context 'when the user is logged in' do + include_context :logged_in_user + + context 'when the comment is saved' do + it 'responds with an OK status' do + post :create, params: valid_comment_params + expect(response.status).to eq(200) + end + end + + context 'when the comment is not saved' do + it 'renders correct response' do + post :create, params: invalid_comment_params + expect(response.body).to eq({}.to_json) + end + end + end + + context 'when the user is not logged in' do + before { post :create } + it_behaves_like :with_no_logged_in_user + end + end + + context 'from a meeting' do + let(:commentable) { create(:meeting) } + let(:commentable_type) { 'meeting' } + let(:valid_comment_params) do + { comment: comment.attributes.merge(commentable_id: commentable.id) } + end + let(:invalid_comment_params) { { comment: comment.attributes } } + + context 'when the user is logged in' do + include_context :logged_in_user + + context 'when the comment is saved' do + it 'responds with an OK status' do + post :create, params: valid_comment_params + expect(response.status).to eq(200) + end + end + + context 'when the comment is not saved' do + it 'renders correct response' do + post :create, params: invalid_comment_params + expect(response.body).to eq({}.to_json) + end + end + end + + context 'when the user is not logged in' do + before { post :create } + + it_behaves_like :with_no_logged_in_user + end + end + end + + describe 'DELETE delete' do + context 'from a moment' do + let(:commentable_type) { 'moment' } + + context 'when the user is logged in' do + include_context :logged_in_user + + context 'when the comment exists and belongs to the current_user' do + let!(:commentable) { create(:moment, user_id: user.id) } + let!(:comment) { create( + :comment, + comment_by: user.id, + commentable_id: commentable.id, + visibility: 'all', + commentable_type: commentable_type + ) } + + it 'destroys the comment' do + expect { delete :delete, params: { comment_id: comment.id } }.to( + change(Comment, :count).by(-1) + ) + end + + it 'renders correct response' do + delete :delete, params: { comment_id: comment.id } + expect(response.body).to eq({ id: comment.id }.to_json) + end + end + + context 'when the comment exists and the commentable belongs to the current_user' do + let!(:commentable) { create(:moment, user_id: user.id) } + let!(:comment) do + create( + :comment, + comment_by: user.id, + commentable_id: commentable.id, + visibility: 'all', + commentable_type: commentable_type + ) + end + + it 'destroys the comment' do + expect { delete :delete, params: { comment_id: comment.id } }.to( + change(Comment, :count).by(-1) + ) + end + + it 'renders correct response' do + comment + delete :delete, params: { comment_id: comment.id } + expect(response.body).to eq({ id: comment.id }.to_json) + end + end + + context 'when the comment does not exist' do + it 'renders correct response' do + delete :delete, params: { comment_id: 0 } + expect(response.body).to eq({}.to_json) + end + end + end + + context 'when the user is not logged in' do + before do + delete :delete + end + it_behaves_like :with_no_logged_in_user + end + end + + context 'from a strategy' do + let(:commentable_type) { 'strategy' } + + context 'when the user is logged in' do + include_context :logged_in_user + + context 'when the comment exists and belongs to the current_user' do + let!(:commentable) { create(:strategy, user_id: user.id) } + let!(:comment) { create( + :comment, + comment_by: user.id, + commentable_id: commentable.id, + visibility: 'all', + commentable_type: commentable_type + ) } + + it 'destroys the comment' do + expect { delete :delete, params: { comment_id: comment.id } }.to( + change(Comment, :count).by(-1) + ) + end + + it 'renders correct response' do + delete :delete, params: { comment_id: comment.id } + expect(response.body).to eq({ id: comment.id }.to_json) + end + end + + context 'when the comment exists and the commentable belongs to the current_user' do + let!(:commentable) { create(:strategy, user_id: user.id) } + let!(:comment) do + create( + :comment, + comment_by: user.id, + commentable_id: commentable.id, + visibility: 'all', + commentable_type: commentable_type + ) + end + + it 'destroys the comment' do + expect { delete :delete, params: { comment_id: comment.id } }.to( + change(Comment, :count).by(-1) + ) + end + + it 'renders correct response' do + comment + delete :delete, params: { comment_id: comment.id } + expect(response.body).to eq({ id: comment.id }.to_json) + end + end + + context 'when the comment does not exist' do + it 'renders correct response' do + delete :delete, params: { comment_id: 0 } + expect(response.body).to eq({}.to_json) + end + end + end + + context 'when the user is not logged in' do + before do + delete :delete + end + it_behaves_like :with_no_logged_in_user + end + end + + context 'from a meeting' do + let(:commentable) { create(:meeting) } + let(:commentable_type) { 'meeting' } + let!(:meeting_member) do + create(:meeting_member, meeting_id: commentable.id, user: user, leader: true) + end + let!(:comment) do + create( + :comment, + comment_by: user.id, + commentable_id: commentable.id, + commentable_type: commentable_type, + visibility: 'all' + ) + end + + context 'when the user is logged in' do + include_context :logged_in_user + + context 'when the comment exists and belongs to the current_user' do + it 'destroys the comment' do + expect { delete :delete, params: { comment_id: comment.id } }.to( + change(Comment, :count).by(-1) + ) + end + + it 'renders correct response' do + delete :delete, params: { comment_id: comment.id } + expect(response.body).to eq({ id: comment.id }.to_json) + end + end + + context 'when the comment exists and the commentable belongs to the current_user' do + let!(:comment) do + create( + :comment, + comment_by: user.id, + commentable_id: commentable.id, + visibility: 'all', + commentable_type: commentable_type + ) + end + + it 'destroys the comment' do + expect { delete :delete, params: { comment_id: comment.id } }.to( + change(Comment, :count).by(-1) + ) + end + + it 'renders correct response' do + comment + delete :delete, params: { comment_id: comment.id } + expect(response.body).to eq({ id: comment.id }.to_json) + end + end + + context 'when the comment does not exist' do + it 'renders correct response' do + delete :delete, params: { comment_id: 0 } + expect(response.body).to eq({}.to_json) + end + end + end + + context 'when the user is not logged in' do + before do + delete :delete + end + + it_behaves_like :with_no_logged_in_user + end + end + end +end diff --git a/spec/controllers/meetings_controller_spec.rb b/spec/controllers/meetings_controller_spec.rb index 52c288f7a4..2d8ee0f555 100644 --- a/spec/controllers/meetings_controller_spec.rb +++ b/spec/controllers/meetings_controller_spec.rb @@ -6,10 +6,9 @@ # TODO: implement session controller # it_behaves_like 'LoggedOut' describe 'GET' do - %w[join leave comment delete_comment].each do |action| + %w[join leave].each do |action| it "#{action} redirects to login" do get action - expect(response).to redirect_to('/users/sign_in') end end @@ -70,114 +69,6 @@ end end - describe 'POST #comment' do - let(:user) { create(:user) } - let(:meeting) { create(:meeting) } - let(:comment) { build(:comment, comment_by: user.id, commentable_type: 'meeting') } - let(:valid_comment_params) do - comment.attributes.merge(commentable_id: meeting.id) - end - let(:invalid_comment_params) { comment.attributes } - - context 'when the user is logged in' do - include_context :logged_in_user - - context 'when the comment is saved' do - it 'responds with an OK status' do - post :comment, params: valid_comment_params - expect(response.status).to eq(200) - end - end - - context 'when the comment is not saved' do - it 'responds with json no_save: true' do - post :comment, params: invalid_comment_params - expect(response.body).to eq({ no_save: true }.to_json) - end - end - end - - context 'when the user is not logged in' do - before { post :comment } - - it_behaves_like :with_no_logged_in_user - end - end - - describe 'GET #delete_comment' do - let(:user) { create(:user, id: 1) } - let(:meeting) { create(:meeting, id: 1) } - let!(:meeting_member) do - create(:meeting_member, meeting: meeting, user: user, leader: true) - end - let!(:comment) do - create( - :comment, - id: 1, - comment_by: 1, - commentable_id: 1, - commentable_type: 'meeting', - visibility: 'all' - ) - end - - context 'when the user is logged in' do - include_context :logged_in_user - - context 'when the comment exists and belongs to the current_user' do - it 'destroys the comment' do - expect { get :delete_comment, params: { commentid: 1 } }.to( - change(Comment, :count).by(-1) - ) - end - - it 'renders nothing' do - get :delete_comment, params: { commentid: 1 } - - expect(response.body).to eq('') - end - end - - context 'when the comment exists and the strategy belongs to the current_user' do - let!(:comment) do - create( - :comment, id: 1, comment_by: 1, commentable_id: 1, visibility: 'all' - ) - end - - let!(:new_moment) { create(:moment, id: 1, user_id: 1) } - - it 'destroys the comment' do - expect { get :delete_comment, params: { commentid: 1 } }.to( - change(Comment, :count).by(-1) - ) - end - - it 'renders nothing' do - comment - get :delete_comment, params: { commentid: 1 } - - expect(response.body).to eq('') - end - end - - context 'when the comment does not exist' do - it 'renders nothing' do - get :delete_comment, params: { commentid: 99 } - expect(response.body).to eq('') - end - end - end - - context 'when the user is not logged in' do - before do - get :delete_comment - end - - it_behaves_like :with_no_logged_in_user - end - end - describe 'GET #new' do let!(:user) { create(:user) } let!(:group_member) do diff --git a/spec/controllers/moments_controller_spec.rb b/spec/controllers/moments_controller_spec.rb index 3e4d52e786..e064f2affd 100644 --- a/spec/controllers/moments_controller_spec.rb +++ b/spec/controllers/moments_controller_spec.rb @@ -78,107 +78,6 @@ def post_create(moment_params) end end - describe 'POST comment' do - let(:user) { create(:user) } - let(:moment) { create(:moment, user: user) } - let(:comment) { build(:comment, comment_by: user.id) } - let(:valid_comment_params) do - comment.attributes.merge(commentable_id: moment.id, visibility: 'all') - end - let(:invalid_comment_params) { comment.attributes } - - context 'when the user is logged in' do - include_context :logged_in_user - - context 'when the comment is saved' do - it 'responds with an OK status' do - post :comment, params: valid_comment_params - expect(response.status).to eq(200) - end - end - - context 'when the comment is not saved' do - it 'responds with json no_save: true' do - post :comment, params: invalid_comment_params - expect(response.body).to eq({ no_save: true }.to_json) - end - end - end - - context 'when the user is not logged in' do - before { post :comment } - - it_behaves_like :with_no_logged_in_user - end - end - - describe 'GET delete_comment' do - let(:user) { create(:user, id: 1) } - - context 'when the user is logged in' do - include_context :logged_in_user - - context 'when the comment exists and belongs to the current_user' do - let!(:new_moment) { create(:moment, id: 1, user_id: 1) } - let!(:comment) do - create( - :comment, id: 1, comment_by: 1, commentable_id: 1, visibility: 'all' - ) - end - - it 'destroys the comment' do - expect { get :delete_comment, params: { commentid: 1 } }.to( - change(Comment, :count).by(-1) - ) - end - - it 'renders nothing' do - get :delete_comment, params: { commentid: 1 } - - expect(response.body).to eq('') - end - end - - context 'when the comment exists and the strategy belongs to the current_user' do - let!(:comment) do - create( - :comment, id: 1, comment_by: 1, commentable_id: 1, visibility: 'all' - ) - end - let!(:new_moment) { create(:moment, id: 1, user_id: 1) } - - it 'destroys the comment' do - expect { get :delete_comment, params: { commentid: 1 } }.to( - change(Comment, :count).by(-1) - ) - end - - it 'renders nothing' do - comment - get :delete_comment, params: { commentid: 1 } - - expect(response.body).to eq('') - end - end - - context 'when the comment does not exist' do - it 'renders nothing' do - get :delete_comment, params: { commentid: 1 } - - expect(response.body).to eq('') - end - end - end - - context 'when the user is not logged in' do - before do - get :delete_comment - end - - it_behaves_like :with_no_logged_in_user - end - end - describe 'Moment Analytic Charts' do it 'should contain react analytics objects' do create_time = Date.current @@ -188,9 +87,7 @@ def post_create(moment_params) new_mood = create(:mood, user_id: new_user.id) create(:moment, user_id: new_user.id, category: Array.new(1, new_category.id), mood: Array.new(1, new_mood.id), created_at: create_time) - get :index - expect(assigns(:react_moments)).to have_key(create_time) expect(assigns(:react_moments)[create_time]).to eq(1) end diff --git a/spec/controllers/secret_shares_controller_spec.rb b/spec/controllers/secret_shares_controller_spec.rb index 5a36887ac2..5cfedb5052 100644 --- a/spec/controllers/secret_shares_controller_spec.rb +++ b/spec/controllers/secret_shares_controller_spec.rb @@ -18,6 +18,7 @@ def do_post_create it 'Creates Secret Share Identifier' do expect(moment.reload.secret_share_identifier).not_to be_nil + expect(response).to redirect_to moment_path(moment, anchor: 'secretShare') end end diff --git a/spec/controllers/strategies_controller_spec.rb b/spec/controllers/strategies_controller_spec.rb index 04cc3b9be2..ee4ed3aff2 100644 --- a/spec/controllers/strategies_controller_spec.rb +++ b/spec/controllers/strategies_controller_spec.rb @@ -73,95 +73,6 @@ end end - describe 'POST comment' do - let(:comment) do - build(:comment, comment_by: user.id, commentable_type: 'strategy') - end - let(:valid_comment_params) do - comment.attributes.merge( - 'commentable_id' => strategy.id, 'visibility' => 'all' - ) - end - let(:invalid_comment_params) { comment.attributes } - - context 'when the user is logged in' do - include_context :logged_in_user - - context 'when the comment is saved' do - it 'responds with an OK status' do - post :comment, params: valid_comment_params - expect(response).to have_http_status(:ok) - end - end - - context 'when the comment is not saved' do - it 'responds with json no_save: true' do - post :comment, params: invalid_comment_params - expect(response.body).to eq({ no_save: true }.to_json) - end - end - end - - context 'when the user is not logged in' do - before { post :comment } - it_behaves_like :with_no_logged_in_user - end - end - - describe 'GET delete_comment' do - context 'when the user is logged in' do - include_context :logged_in_user - - context 'when the comment exists' do - let!(:comment) do - create( - :comment, - comment_by: user.id, - commentable_id: strategy.id, - visibility: 'all' - ) - end - - context 'when the comment belongs to the current_user' do - it 'destroys the comment' do - expect { get :delete_comment, params: { commentid: comment.id } } - .to change(Comment, :count).by(-1) - end - - it 'renders nothing' do - get :delete_comment, params: { commentid: comment.id } - expect(response.body).to be_empty - end - end - - context 'when the strategy belongs to the current_user' do - it 'destroys the comment' do - expect { get :delete_comment, params: { commentid: comment.id } } - .to change(Comment, :count).by(-1) - end - - it 'renders nothing' do - comment - get :delete_comment, params: { commentid: 1 } - expect(response.body).to be_empty - end - end - end - - context 'when the comment does not exist' do - it 'renders nothing' do - get :delete_comment, params: { commentid: 1 } - expect(response.body).to be_empty - end - end - end - - context 'when the user is not logged in' do - before { get :delete_comment } - it_behaves_like :with_no_logged_in_user - end - end - describe 'POST premade' do context 'when the user is logged in' do include_context :logged_in_user diff --git a/spec/features/user_creates_a_draft_moment_spec.rb b/spec/features/user_creates_a_draft_moment_spec.rb index 71a25a7ba6..8afb6547a9 100644 --- a/spec/features/user_creates_a_draft_moment_spec.rb +++ b/spec/features/user_creates_a_draft_moment_spec.rb @@ -100,7 +100,7 @@ expect(page).to have_content 'Some New Strategy' find('.storyActionsViewers').hover expect(page).to have_content 'Ally 1' - expect(page).to have_css('#new_comment') + expect(page).not_to have_css('#comments') expect(page).to have_selector '.storyDraft' back = current_url @@ -153,6 +153,7 @@ expect(find('.pageTitle')).to have_content 'My New Moment' expect(page).to have_content moment_why_text expect(page).not_to have_selector '.storyDraft' + expect(page).to have_css('#comments') # TRYING TO VIEW AS ALLY login_as ally diff --git a/spec/features/user_creates_a_draft_strategy_spec.rb b/spec/features/user_creates_a_draft_strategy_spec.rb index 4399637dc2..36550f8a06 100644 --- a/spec/features/user_creates_a_draft_strategy_spec.rb +++ b/spec/features/user_creates_a_draft_strategy_spec.rb @@ -65,7 +65,7 @@ find('.storyActionsViewers').hover expect(page).to have_content 'Ally 1' expect(page).to have_content 'Daily reminder email' - expect(page).to have_css('#new_comment') + expect(page).not_to have_css('#comments') expect(page).to have_selector '.storyDraft' back = current_url @@ -104,6 +104,7 @@ expect(find('.pageTitle')).to have_content 'My New Strategy' expect(page).to have_content strategy_description_text expect(page).not_to have_selector '.storyDraft' + expect(page).to have_css('#comments') # TRYING TO VIEW AS ALLY login_as ally diff --git a/spec/features/user_creates_a_published_moment_spec.rb b/spec/features/user_creates_a_published_moment_spec.rb index a7d2c4b61a..d464bc81c9 100644 --- a/spec/features/user_creates_a_published_moment_spec.rb +++ b/spec/features/user_creates_a_published_moment_spec.rb @@ -108,7 +108,7 @@ expect(page).to have_content 'Some New Strategy' find('.storyActionsViewers').hover expect(page).to have_content 'Ally 0, Ally 1, and Ally 2' - expect(page).to have_css('#new_comment') + expect(page).to have_css('#comments') expect(page).not_to have_selector '.storyDraft' # EDITING diff --git a/spec/features/user_creates_a_published_strategy_spec.rb b/spec/features/user_creates_a_published_strategy_spec.rb index 9f5b114cca..a435bf105b 100644 --- a/spec/features/user_creates_a_published_strategy_spec.rb +++ b/spec/features/user_creates_a_published_strategy_spec.rb @@ -70,7 +70,7 @@ find('.storyActionsViewers').hover expect(page).to have_content 'Ally 0, Ally 1, and Ally 2' expect(page).to have_content 'Daily reminder email' - expect(page).to have_css('#new_comment') + expect(page).to have_css('#comments') expect(page).not_to have_selector '.storyDraft' # EDITING diff --git a/spec/helpers/comment_form_helper_spec.rb b/spec/helpers/comment_form_helper_spec.rb new file mode 100644 index 0000000000..aed9bc6cef --- /dev/null +++ b/spec/helpers/comment_form_helper_spec.rb @@ -0,0 +1,228 @@ +# frozen_string_literal: true + +describe CommentFormHelper, type: :controller do + controller(ApplicationController) do + end + + describe '#comment_form_props' do + let(:user1) { create(:user1) } + let(:user2) { create(:user2) } + let(:submit_input) do + { + id: 'submit', + type: 'submit', + value: 'Submit', + dark: true + } + end + + def default_inputs(comment_by, commentable_id, commentable_type) + [ + { + id: 'comment_commentable_type', + name: 'comment[commentable_type]', + type: 'hidden', + value: commentable_type + }, + { + id: 'comment_comment_by', + name: 'comment[comment_by]', + type: 'hidden', + value: comment_by + }, + { + id: 'comment_commentable_id', + name: 'comment[commentable_id]', + type: 'hidden', + value: commentable_id + }, + { + id: 'comment_comment', + name: 'comment[comment]', + type: 'textarea', + dark: true, + value: nil, + required: true, + label: 'Comment' + }, + ] + end + + def non_owner_inputs(commentable_type) + { + inputs: default_inputs(user1.id, commentable.id, commentable_type).concat([ + options: [ + { + id: 'comment_visibility_all', + label: 'Share with everyone', + value: 'all' + }, + { + id: 'comment_visibility_private', + label: 'Share with Plum Blossom only', + value: 'private' + } + ], + id: 'comment_visibility', + name: 'comment[visibility]', + type: 'select', + value: 'all', + dark: true + ]).concat([submit_input]), + action: comment_index_path, + noFormTag: true + } + end + + def owner_has_no_viewers_inputs(commentable_type) + { + inputs: default_inputs(user1.id, commentable.id, commentable_type).concat([submit_input]), + action: comment_index_path, + noFormTag: true + + } + end + + def owner_has_viewers_inputs(commentable_type) + { + inputs: default_inputs(user1.id, commentable.id, commentable_type).concat([ + options: [ + { + id: 'comment_viewers_everyone', + label: 'Share with everyone', + value: '' + }, + { + id: "comment_viewers_#{user2.id}", + label: 'Share with Plum Blossom only', + value: user2.id + } + ], + id: 'comment_viewers', + name: 'comment[viewers]', + type: 'select', + value: '', + dark: true + ]).concat([submit_input]), + action: comment_index_path, + noFormTag: true + } + end + + subject { controller.comment_form_props(commentable, commentable_type) } + + before do + allow(controller).to receive(:current_user).and_return(user1) + end + + context 'commentable type is a moment' do + let(:commentable_type) { 'moment' } + + context 'user is the commentable owner' do + let(:commentable) { create(:moment, user_id: user1.id, comment: true, viewers: viewers) } + + context 'has no viewers' do + let(:viewers) { nil } + + it 'returns correct props' do + expect(subject).to eq(owner_has_no_viewers_inputs('moment')) + end + end + + context 'has viewers' do + let(:viewers) { [user2.id] } + + it 'returns correct props' do + expect(subject).to eq(owner_has_viewers_inputs('moment')) + end + end + end + + context 'user is not the commentable owner' do + let(:commentable) { create(:moment, user_id: user2.id, comment: true, viewers: viewers) } + + context 'has no viewers' do + let(:viewers) { nil } + + it 'returns correct props' do + expect(subject).to eq(non_owner_inputs('moment')) + end + end + + context 'has viewers' do + let(:viewers) { [user2.id] } + + it 'returns correct props' do + expect(subject).to eq(non_owner_inputs('moment')) + end + end + end + end + + context 'commentable type is a strategy' do + let(:commentable_type) { 'strategy' } + + context 'user is the commentable owner' do + let(:commentable) { create(:strategy, user_id: user1.id, comment: true, viewers: viewers) } + + context 'has no viewers' do + let(:viewers) { nil } + + it 'returns correct props' do + expect(subject).to eq(owner_has_no_viewers_inputs('strategy')) + end + end + + context 'has viewers' do + let(:viewers) { [user2.id] } + + it 'returns correct props' do + expect(subject).to eq(owner_has_viewers_inputs('strategy')) + end + end + end + + context 'user is not the commentable owner' do + let(:commentable) { create(:strategy, user_id: user2.id, comment: true, viewers: viewers) } + + context 'has no viewers' do + let(:viewers) { nil } + + it 'returns correct props' do + expect(subject).to eq(non_owner_inputs('strategy')) + end + end + + context 'has viewers' do + let(:viewers) { [user2.id] } + + it 'returns correct props' do + expect(subject).to eq(non_owner_inputs('strategy')) + end + end + end + end + + context 'commentable type is a meeting' do + let(:commentable_type) { 'meeting' } + + context 'user is a meeting leader' do + let(:commentable) { create(:meeting) } + let(:meeting_members) { create(:meeting_members, user_id: user1.id, meeting_id: commentable.id, leader: true)} + + it 'returns correct props' do + expect(subject).to eq(owner_has_no_viewers_inputs('meeting')) + end + end + + context 'user is not a meeting leader' do + let(:commentable) { create(:meeting) } + let(:meeting_members) { create(:meeting_members, user_id: user1.id, meeting_id: commentable.id, leader: false)} + + it 'returns correct props' do + expect(subject).to eq(owner_has_no_viewers_inputs('meeting')) + end + end + end + end +end diff --git a/spec/helpers/comments_helper_spec.rb b/spec/helpers/comments_helper_spec.rb index 0bfd157c05..e11cc774b0 100644 --- a/spec/helpers/comments_helper_spec.rb +++ b/spec/helpers/comments_helper_spec.rb @@ -7,17 +7,10 @@ controller(ApplicationController) do end - describe 'generate_comment' do + describe '#generate_comments' do let(:user3) { create(:user3) } let(:comment) { 'Hello from the outside' } - - def delete_comment(comment_id) - %(
) - end - - def comment_info(user) - %(#{user.name} - less than a minute ago) - end + let(:created_at) { 'Created less than a minute ago' } before do create(:allyships_accepted, user_id: user1.id, ally_id: user2.id) @@ -34,26 +27,30 @@ def comment_info(user) it 'generates a valid comment object when visbility is all' do new_comment = create(:comment, comment: comment, commentable_type: 'moment', commentable_id: new_moment.id, comment_by: user1.id, visibility: 'all') - expect(controller.generate_comment(new_comment, 'moment')).to include( - commentid: new_comment.id, - comment_info: comment_info(user1), - comment_text: comment, - visibility: nil, - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user1.uid, + commentByName: user1.name, + commentByAvatar: user1.avatar.url, + comment: comment, + createdAt: created_at, + viewers: nil, + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end it 'generates a valid comment object when visbility is private' do new_comment = create(:comment, comment: comment, commentable_type: 'moment', commentable_id: new_moment.id, comment_by: user1.id, visibility: 'private', viewers: [user2.id]) - expect(controller.generate_comment(new_comment, 'moment')).to include( - commentid: new_comment.id, - comment_info: comment_info(user1), - comment_text: comment, - visibility: "Visible only between you and #{user2.name}", - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user1.uid, + commentByName: user1.name, + commentByAvatar: user1.avatar.url, + comment: comment, + createdAt: created_at, + viewers: "Visible only between you and #{user2.name}", + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end end @@ -64,26 +61,30 @@ def comment_info(user) it 'generates a valid comment object when visbility is all' do new_comment = create(:comment, comment: comment, commentable_type: 'moment', commentable_id: new_moment.id, comment_by: user2.id, visibility: 'all') - expect(controller.generate_comment(new_comment, 'moment')).to include( - commentid: new_comment.id, - comment_info: comment_info(user2), - comment_text: comment, - visibility: nil, - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user2.uid, + commentByName: user2.name, + commentByAvatar: user2.avatar.url, + comment: comment, + createdAt: created_at, + viewers: nil, + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end it 'generates a valid comment object when visbility is private' do new_comment = create(:comment, comment: comment, commentable_type: 'moment', commentable_id: new_moment.id, comment_by: user2.id, visibility: 'private', viewers: [user1.id]) - expect(controller.generate_comment(new_comment, 'moment')).to include( - commentid: new_comment.id, - comment_info: comment_info(user2), - comment_text: comment, - visibility: "Visible only between you and #{user1.name}", - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user2.uid, + commentByName: user2.name, + commentByAvatar: user2.avatar.url, + comment: comment, + createdAt: created_at, + viewers: "Visible only between you and #{user1.name}", + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end end end @@ -98,26 +99,30 @@ def comment_info(user) it 'generates a valid comment object when visbility is all' do new_comment = create(:comment, comment: comment, commentable_type: 'strategy', commentable_id: new_strategy.id, comment_by: user1.id, visibility: 'all') - expect(controller.generate_comment(new_comment, 'strategy')).to include( - commentid: new_comment.id, - comment_info: comment_info(user1), - comment_text: comment, - visibility: nil, - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user1.uid, + commentByName: user1.name, + commentByAvatar: user1.avatar.url, + comment: comment, + createdAt: created_at, + viewers: nil, + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end it 'generates a valid comment object when visbility is private' do new_comment = create(:comment, comment: comment, commentable_type: 'strategy', commentable_id: new_strategy.id, comment_by: user1.id, visibility: 'private', viewers: [user2.id]) - expect(controller.generate_comment(new_comment, 'strategy')).to include( - commentid: new_comment.id, - comment_info: comment_info(user1), - comment_text: comment, - visibility: "Visible only between you and #{user2.name}", - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user1.uid, + commentByName: user1.name, + commentByAvatar: user1.avatar.url, + comment: comment, + createdAt: created_at, + viewers: "Visible only between you and #{user2.name}", + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end end @@ -128,26 +133,30 @@ def comment_info(user) it 'generates a valid comment object when visbility is all' do new_comment = create(:comment, comment: comment, commentable_type: 'strategy', commentable_id: new_strategy.id, comment_by: user2.id, visibility: 'all') - expect(controller.generate_comment(new_comment, 'strategy')).to include( - commentid: new_comment.id, - comment_info: comment_info(user2), - comment_text: comment, - visibility: nil, - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user2.uid, + commentByName: user2.name, + commentByAvatar: user2.avatar.url, + comment: comment, + createdAt: created_at, + viewers: nil, + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end it 'generates a valid comment object when visbility is private' do new_comment = create(:comment, comment: comment, commentable_type: 'strategy', commentable_id: new_strategy.id, comment_by: user2.id, visibility: 'private', viewers: [user1.id]) - expect(controller.generate_comment(new_comment, 'strategy')).to include( - commentid: new_comment.id, - comment_info: comment_info(user2), - comment_text: comment, - visibility: "Visible only between you and #{user1.name}", - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user2.uid, + commentByName: user2.name, + commentByAvatar: user2.avatar.url, + comment: comment, + createdAt: created_at, + viewers: "Visible only between you and #{user1.name}", + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end end end @@ -164,14 +173,16 @@ def comment_info(user) it 'generates a valid comment object' do sign_in user1 new_comment = create(:comment, comment: comment, commentable_type: 'meeting', commentable_id: new_meeting.id, comment_by: user1.id, visibility: 'all') - expect(controller.generate_comment(new_comment, 'meeting')).to include( - commentid: new_comment.id, - comment_info: comment_info(user1), - comment_text: comment, - visibility: nil, - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user1.uid, + commentByName: user1.name, + commentByAvatar: user1.avatar.url, + comment: comment, + createdAt: created_at, + viewers: nil, + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end end @@ -179,14 +190,16 @@ def comment_info(user) it 'generates a valid comment object' do sign_in user2 new_comment = create(:comment, comment: comment, commentable_type: 'meeting', commentable_id: new_meeting.id, comment_by: user2.id, visibility: 'all') - expect(controller.generate_comment(new_comment, 'meeting')).to include( - commentid: new_comment.id, - comment_info: comment_info(user2), - comment_text: comment, - visibility: nil, - delete_comment: delete_comment(new_comment.id), - no_save: false - ) + expect(controller.generate_comments(Comment.where(id: new_comment.id))).to eq([{ + id: new_comment.id, + commentByUid: user2.uid, + commentByName: user2.name, + commentByAvatar: user2.avatar.url, + comment: comment, + createdAt: created_at, + viewers: nil, + deleteAction: delete_comment_index_path(comment_id: new_comment.id) + }]) end end end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 4336aefbc8..5b88e53918 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -85,6 +85,58 @@ end end + describe 'comments' do + before do + create( + :comment, + commentable_id: 0, + commentable_type: commentable_type, + comment_by: user2.id, + comment: short_comment, + visibility: 'all' + ) + create( + :comment, + commentable_id: commentable.id, + commentable_type: commentable_type, + comment_by: user2.id, + comment: short_comment, + visibility: 'all' + ) + end + + context 'when commentable type is a moment' do + let(:commentable) { create(:moment, comment: true, user_id: user1.id, viewers: [user2.id]) } + let(:commentable_type) { 'moment' } + + it 'returns correct number of comments' do + expect(Comment.comments_from(commentable).count).to eq(1) + expect(Comment.count).to eq(2) + end + end + + context 'when commentable type is a strategy' do + let(:commentable) { create(:strategy, comment: true, user_id: user1.id, viewers: [user2.id]) } + let(:commentable_type) { 'strategy' } + + it 'returns correct number of comments' do + expect(Comment.comments_from(commentable).count).to eq(1) + expect(Comment.count).to eq(2) + end + end + + context 'when commentable type is a meeting' do + let(:commentable) { create(:meeting) } + let(:commentable_type) { 'meeting' } + + it 'returns correct number of comments' do + create :meeting_member, user_id: user1.id, leader: true, meeting_id: commentable.id + expect(Comment.comments_from(commentable).count).to eq(1) + expect(Comment.count).to eq(2) + end + end + end + describe 'notify_of_creation!' do context 'Moments' do it 'does not send a notification when the user created both the Moment and comment' do diff --git a/spec/services/comment_viewers_service_spec.rb b/spec/services/comment_viewers_service_spec.rb new file mode 100644 index 0000000000..27b223af3d --- /dev/null +++ b/spec/services/comment_viewers_service_spec.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true + +describe CommentViewersService do + let(:owner) { FactoryBot.create(:user2, :with_allies) } + let(:ally) { owner.allies.first } + let(:ally_commenter) { owner.allies.second } + let(:strategy) { FactoryBot.create(:strategy, user_id: owner.id) } + let(:moment) { FactoryBot.create(:moment, user_id: owner.id) } + let(:commentable) do + { + strategy: strategy, + moment: moment + } + end + %i[strategy moment].each do |commentable_name| + let(:my_commentable) { commentable[commentable_name] } + let(:comment) do + Comment.create!(commentable_type: commentable_name, + commentable_id: my_commentable.id, + comment_by: commenter.id, + comment: 'test comment', + visibility: visibility, + viewers: viewers) + end + end + + describe '#viewers' do + subject { CommentViewersService.viewers(comment, current_user) } + + context 'private comments (visible to you and 1 ally)' do + let(:visibility) { 'private' } + + context 'and comment was made by owner' do + let(:commenter) { owner } + let(:viewers) { [ally.id] } + + context 'logged in as owner' do + let(:current_user) { owner } + + it "has the ally's name in visibility" do + expect(subject).to eq("Visible only between you and #{ally.name}") + end + end + + context 'logged in as ally' do + let(:current_user) { ally } + + it "has the owner's name in visibility" do + expect(subject).to eq("Visible only between you and #{owner.name}") + end + end + end + + context 'and comment was made by an ally' do + let(:commenter) { ally_commenter } + let(:viewers) { [] } + + context 'logged in as owner' do + let(:current_user) { owner } + + it "has the ally's name in visibility" do + expect(subject).to eq("Visible only between you and #{ally_commenter.name}") + end + end + + context 'logged in as commenter' do + let(:current_user) { ally_commenter } + + it "has the owner's name in visibility" do + expect(subject).to eq("Visible only between you and #{owner.name}") + end + end + end + end + + context 'public comments (visible to all allies)' do + let(:visibility) { 'all' } + let(:viewers) { [] } + + context 'and comment was made by owner' do + let(:commenter) { owner } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'has nothing for visibility' do + expect(subject).to be_nil + end + end + + context 'logged in as ally' do + let(:current_user) { ally } + + it 'has nothing for visibility' do + expect(subject).to be_nil + end + end + end + + context 'and comment was made by ally' do + let(:commenter) { ally_commenter } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'has nothing for visibility' do + expect(subject).to be_nil + end + end + + context 'logged in as commenter' do + let(:current_user) { commenter } + + it 'has nothing for visibility' do + expect(subject).to be_nil + end + end + end + end + end + + describe '#viewable?' do + subject { CommentViewersService.viewable?(comment, current_user) } + + context 'private comments (visible to you and 1 ally)' do + let(:visibility) { 'private' } + + context 'and comment was made by owner' do + let(:commenter) { owner } + let(:viewers) { [ally.id] } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as ally' do + let(:current_user) { ally } + + it 'returns true' do + expect(subject).to eq(true) + end + end + end + + context 'and comment was made by an ally' do + let(:commenter) { ally_commenter } + let(:viewers) { [] } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as commenter' do + let(:current_user) { ally_commenter } + + it 'returns true' do + expect(subject).to eq(true) + end + end + end + end + + context 'public comments (visible to all allies)' do + let(:visibility) { 'all' } + let(:viewers) { [] } + + context 'and comment was made by owner' do + let(:commenter) { owner } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as ally' do + let(:current_user) { ally } + + it 'returns false' do + expect(subject).to eq(false) + end + end + end + + context 'and comment was made by ally' do + let(:commenter) { ally_commenter } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as commenter' do + let(:current_user) { commenter } + + it 'returns true' do + expect(subject).to eq(true) + end + end + end + end + end + + describe '#deletable?' do + subject { CommentViewersService.deletable?(comment, current_user) } + + context 'private comments (visible to you and 1 ally)' do + let(:visibility) { 'private' } + + context 'and comment was made by owner' do + let(:commenter) { owner } + let(:viewers) { [ally.id] } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as ally' do + let(:current_user) { ally } + + it 'returns false' do + expect(subject).to eq(false) + end + end + end + + context 'and comment was made by an ally' do + let(:commenter) { ally_commenter } + let(:viewers) { [] } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as commenter' do + let(:current_user) { ally_commenter } + + it 'returns true' do + expect(subject).to eq(true) + end + end + end + end + + context 'public comments (visible to all allies)' do + let(:visibility) { 'all' } + let(:viewers) { [] } + + context 'and comment was made by owner' do + let(:commenter) { owner } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as ally' do + let(:current_user) { ally } + + it 'returns false' do + expect(subject).to eq(false) + end + end + end + + context 'and comment was made by ally' do + let(:commenter) { ally_commenter } + + context 'logged in as owner' do + let(:current_user) { owner } + + it 'returns true' do + expect(subject).to eq(true) + end + end + + context 'logged in as commenter' do + let(:current_user) { commenter } + + it 'returns true' do + expect(subject).to eq(true) + end + end + end + end + end +end diff --git a/spec/services/comment_visibility_spec.rb b/spec/services/comment_visibility_spec.rb deleted file mode 100644 index 29b3fc978f..0000000000 --- a/spec/services/comment_visibility_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -describe CommentVisibility do - describe '#build' do - let(:owner) { FactoryBot.create(:user2, :with_allies) } - let(:ally) { owner.allies.first } - let(:ally_commenter) { owner.allies.second } - - let(:strategy) { FactoryBot.create(:strategy, user_id: owner.id) } - let(:moment) { FactoryBot.create(:moment, user_id: owner.id) } - - let(:commentable) do - { - strategy: strategy, - moment: moment - } - end - - %i[strategy moment].each do |commentable_name| - let(:commentable_id) { commentable[commentable_name] } - - let(:comment) do - Comment.create!(commentable_type: commentable_name, - commentable_id: commentable_id.id, - comment_by: commenter.id, - comment: 'test comment', - visibility: visibility, - viewers: viewers) - end - - subject { CommentVisibility.build(comment, commentable_id, current_user) } - - describe 'private comments (visible to you and 1 ally)' do - let(:visibility) { 'private' } - - describe 'and comment was made by owner' do - let(:commenter) { owner } - let(:viewers) { [ally.id] } - - describe 'logged in as owner' do - let(:current_user) { owner } - - it "has the ally's name in visibility" do - expect(subject).to eq("Visible only between you and #{ally.name}") - end - end - - describe 'logged in as ally' do - let(:current_user) { ally } - - it "has the owner's name in visibility" do - expect(subject).to eq("Visible only between you and #{owner.name}") - end - end - end - - describe 'and comment was made by an ally' do - let(:commenter) { ally_commenter } - let(:viewers) { [] } - - describe 'logged in as owner' do - let(:current_user) { owner } - - it "has the ally's name in visibility" do - expect(subject).to eq("Visible only between you and #{ally_commenter.name}") - end - end - - describe 'logged in as commenter' do - let(:current_user) { ally_commenter } - - it "has the owner's name in visibility" do - expect(subject).to eq("Visible only between you and #{owner.name}") - end - end - end - end - - describe 'public comments (visible to all allies)' do - let(:visibility) { 'all' } - let(:viewers) { [] } - - describe 'and comment was made by owner' do - let(:commenter) { owner } - - describe 'logged in as owner' do - let(:current_user) { owner } - - it 'has nothing for visibility' do - expect(subject).to be_nil - end - end - - describe 'logged in as ally' do - let(:current_user) { ally } - - it 'has nothing for visibility' do - expect(subject).to be_nil - end - end - end - - describe 'and comment was made by ally' do - let(:commenter) { ally_commenter } - - describe 'logged in as owner' do - let(:current_user) { owner } - - it 'has nothing for visibility' do - expect(subject).to be_nil - end - end - - describe 'logged in as commenter' do - let(:current_user) { commenter } - - it 'has nothing for visibility' do - expect(subject).to be_nil - end - end - end - end - end - end -end