diff --git a/app/controllers/boosts_controller.rb b/app/controllers/boosts_controller.rb index 166b7e634..5f5c95e93 100644 --- a/app/controllers/boosts_controller.rb +++ b/app/controllers/boosts_controller.rb @@ -2,6 +2,6 @@ class BoostsController < ApplicationController include BubbleScoped, BucketScoped def create - @bubble.boosts.create! + @bubble.boost! end end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 3a6a650ac..d9ce0616c 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -2,12 +2,7 @@ class CommentsController < ApplicationController include BubbleScoped, BucketScoped def create - @bubble.comments.create!(comment_params) + @bubble.comment! params.dig(:comment, :body).presence redirect_to bucket_bubble_url(@bucket, @bubble) end - - private - def comment_params - params.require(:comment).permit(:body) - end end diff --git a/app/helpers/bubbles_helper.rb b/app/helpers/bubbles_helper.rb index 4d47f7d11..8bdf50648 100644 --- a/app/helpers/bubbles_helper.rb +++ b/app/helpers/bubbles_helper.rb @@ -8,7 +8,7 @@ module BubblesHelper end def bubble_size(bubble) - activity = bubble.boosts.size + bubble.comments.size + activity = bubble.boost_count + bubble.comments.size rank = case activity when 0..5 then "one" diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb deleted file mode 100644 index 18586dbe5..000000000 --- a/app/helpers/comments_helper.rb +++ /dev/null @@ -1,80 +0,0 @@ -module CommentsHelper - def render_comments_and_boosts(bubble) - combined_collection = combine_and_sort_items(bubble) - - safe_join([ - render_creator_summary(bubble, combined_collection), - render_remaining_items(combined_collection) - ]) - end - - private - def combine_and_sort_items(bubble) - (bubble.comments + bubble.boosts + bubble.assignments).sort_by(&:created_at) - end - - def render_creator_summary(bubble, combined_collection) - content_tag(:div, class: "comment--upvotes flex-inline flex-wrap align-start gap fill-white border-radius center position-relative") do - [ - creator_info(bubble), - initial_assignment_info(combined_collection), - render_initial_boosts(combined_collection) - ].compact.join(", ").html_safe - end - end - - def creator_info(bubble) - "Added by #{bubble.creator.name} #{time_ago_in_words(bubble.created_at)} ago" - end - - def initial_assignment_info(combined_collection) - initial_assignment = combined_collection.find { |item| item.is_a?(Assignment) } - "assigned to #{initial_assignment.assignee.name}" if initial_assignment - end - - def render_initial_boosts(combined_collection) - grouped_boosts = combined_collection.take_while { |item| item.is_a?(Boost) } - return if grouped_boosts.empty? - - user_boosts = grouped_boosts.group_by(&:creator).transform_values(&:count) - boost_summaries = user_boosts.map { |user, count| "#{user.name} +#{count}" } - boost_summaries.to_sentence - end - - def render_remaining_items(combined_collection) - initial_count = combined_collection.take_while { |item| !item.is_a?(Comment) }.count - items = combined_collection.drop(initial_count) - - safe_join(items.chunk_while { |i, j| grouped_item?(i) && grouped_item?(j) }.map do |chunk| - if chunk.first.is_a?(Comment) - render "comments/comment", comment: chunk.first - else - render_grouped_items(chunk) - end - end) - end - - def grouped_item?(item) - item.is_a?(Boost) || item.is_a?(Assignment) - end - - def render_grouped_items(items) - return if items.empty? - - content_tag(:div, class: "comment--upvotes flex-inline flex-wrap align-start gap fill-white border-radius center position-relative") do - [ - render_grouped_boosts(items.select { |item| item.is_a?(Boost) }), - render_grouped_assignments(items.select { |item| item.is_a?(Assignment) }) - ].flatten.compact.to_sentence.html_safe - end - end - - def render_grouped_boosts(boosts) - return if boosts.empty? - boosts.group_by(&:creator).map { |user, user_boosts| "#{user.name} +#{user_boosts.count}" } - end - - def render_grouped_assignments(assignments) - assignments.map { |assignment| "Assigned to #{assignment.assignee.name} #{time_ago_in_words(assignment.created_at)} ago" } - end -end diff --git a/app/models/boost.rb b/app/models/boost.rb deleted file mode 100644 index 41b0edb7a..000000000 --- a/app/models/boost.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Boost < ApplicationRecord - belongs_to :bubble - belongs_to :creator, class_name: "User", default: -> { Current.user } -end diff --git a/app/models/bubble.rb b/app/models/bubble.rb index 43a6d05f8..debbf14cd 100644 --- a/app/models/bubble.rb +++ b/app/models/bubble.rb @@ -1,12 +1,9 @@ class Bubble < ApplicationRecord - include Assignable, Colored, Poppable, Searchable, Taggable + include Assignable, Boostable, Colored, Commentable, Eventable, Poppable, Searchable, Taggable, Threaded belongs_to :bucket belongs_to :creator, class_name: "User", default: -> { Current.user } - has_many :comments, dependent: :destroy - has_many :boosts, dependent: :destroy - has_one_attached :image, dependent: :purge_later searchable_by :title, using: :bubbles_search_index @@ -14,7 +11,7 @@ class Bubble < ApplicationRecord before_save :set_default_title scope :reverse_chronologically, -> { order(created_at: :desc, id: :desc) } - scope :ordered_by_activity, -> { left_joins(:comments, :boosts).group(:id).order(Arel.sql("COUNT(comments.id) + COUNT(boosts.id) DESC")) } + scope :ordered_by_activity, -> { left_joins(:comments).group(:id).order(Arel.sql("COUNT(comments.id) + boost_count DESC")) } scope :mentioning, ->(query) do bubbles = search(query).select(:id).to_sql diff --git a/app/models/bubble/assignable.rb b/app/models/bubble/assignable.rb index 3abf18d39..828034dab 100644 --- a/app/models/bubble/assignable.rb +++ b/app/models/bubble/assignable.rb @@ -2,7 +2,7 @@ module Bubble::Assignable extend ActiveSupport::Concern included do - has_many :assignments, dependent: :destroy + has_many :assignments, dependent: :delete_all has_many :assignees, through: :assignments has_many :assigners, through: :assignments @@ -12,8 +12,9 @@ module Bubble::Assignable end def assign(users, assigner: Current.user) - (Array(users) - assignees).tap do |users| - users.each { |user| assignments.create!(assignee: user, assigner: assigner) } + transaction do + Assignment.insert_all Array(users).collect { |user| { assignee_id: user.id, assigner_id: assigner.id, bubble_id: id } } + track_event :assigned, assignee_ids: Array(users).map(&:id) end end end diff --git a/app/models/bubble/boostable.rb b/app/models/bubble/boostable.rb new file mode 100644 index 000000000..98ce28485 --- /dev/null +++ b/app/models/bubble/boostable.rb @@ -0,0 +1,10 @@ +module Bubble::Boostable + extend ActiveSupport::Concern + + def boost! + transaction do + increment! :boost_count + track_event :boosted + end + end +end diff --git a/app/models/bubble/commentable.rb b/app/models/bubble/commentable.rb new file mode 100644 index 000000000..911fd3956 --- /dev/null +++ b/app/models/bubble/commentable.rb @@ -0,0 +1,11 @@ +module Bubble::Commentable + extend ActiveSupport::Concern + + included do + has_many :comments, dependent: :destroy + end + + def comment!(body) + comments.create! body: + end +end diff --git a/app/models/bubble/eventable.rb b/app/models/bubble/eventable.rb new file mode 100644 index 000000000..e1137880a --- /dev/null +++ b/app/models/bubble/eventable.rb @@ -0,0 +1,14 @@ +module Bubble::Eventable + extend ActiveSupport::Concern + + included do + has_many :events, dependent: :delete_all + + after_create -> { track_event :created } + end + + private + def track_event(action, creator: Current.user, **particulars) + events.create! action:, creator:, particulars: + end +end diff --git a/app/models/bubble/thread.rb b/app/models/bubble/thread.rb new file mode 100644 index 000000000..8b0e28be5 --- /dev/null +++ b/app/models/bubble/thread.rb @@ -0,0 +1,35 @@ +class Bubble::Thread + attr_reader :bubble + + def initialize(bubble) + @bubble = bubble + end + + def entries + sorted_entries.chunk_while { |a, b| consecutive_events?(a, b) }.map.with_index { |entries, index| roll_up(entries, index) } + end + + def to_partial_path + "bubbles/threads/thread" + end + + private + delegate :events, :comments, to: :bubble, private: true + + def sorted_entries + (events + comments).sort_by(&:created_at) + end + + def consecutive_events?(a, b) + [ a, b ] in [ Event, Event ] + end + + def roll_up(entries, position) + case entries.first + when Comment + entries.sole + when Event + Rollup.new self, entries, first_position: position.zero? + end + end +end diff --git a/app/models/bubble/thread/rollup.rb b/app/models/bubble/thread/rollup.rb new file mode 100644 index 000000000..dd034aa8b --- /dev/null +++ b/app/models/bubble/thread/rollup.rb @@ -0,0 +1,55 @@ +class Bubble::Thread::Rollup + def initialize(thread, entries, first_position: false) + @thread = thread + @entries = entries + @first_position = first_position + end + + def body + collapsed_entries.map { |entry, chunk_size| summarize(entry, chunk_size) }.to_sentence.upcase_first + end + + def to_partial_path + "bubbles/threads/rollup" + end + + private + attr_reader :thread, :entries, :first_position + + delegate :time_ago_in_words, to: "ApplicationController.helpers", private: true + + def collapsed_entries + sorted_entries.chunk_while { |a, b| repeated_boosts?(a, b) }.map { |chunk| [ chunk.last, chunk.size ] } + end + + def sorted_entries + entries.sort_by do |entry| + case entry.action + when "created" then [ 1, entry.created_at ] + when "assigned" then [ 2, entry.created_at ] + when "boosted" then [ 3, entry.creator, entry.created_at ] + end + end + end + + def repeated_boosts?(a, b) + a.action == "boosted" && a.slice(:action, :creator_id) == b.slice(:action, :creator_id) + end + + def summarize(entry, chunk_size) + case entry.action + when "created" + "added by #{entry.creator.name} #{time_ago_in_words(entry.created_at)} ago" + when "assigned" + summary = "assigned to #{entry.assignee_names.to_sentence}" + summary += " #{time_ago_in_words(entry.created_at)} ago" unless first_position? + summary + when "boosted" + "#{entry.creator.name} +#{chunk_size}" + end + end + + def first_position? + first_position + end +end diff --git a/app/models/bubble/threaded.rb b/app/models/bubble/threaded.rb new file mode 100644 index 000000000..22885f62a --- /dev/null +++ b/app/models/bubble/threaded.rb @@ -0,0 +1,5 @@ +module Bubble::Threaded + def thread + Bubble::Thread.new self + end +end diff --git a/app/models/current.rb b/app/models/current.rb index e5546b93b..d534f3e58 100644 --- a/app/models/current.rb +++ b/app/models/current.rb @@ -1,6 +1,10 @@ class Current < ActiveSupport::CurrentAttributes - attribute :session + attribute :session, :user - delegate :user, to: :session, allow_nil: true delegate :account, to: :user, allow_nil: true + + def session=(session) + super + self.user = session.user + end end diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 000000000..8994acc05 --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,10 @@ +class Event < ApplicationRecord + THREADABLE_ACTIONS = %w[ assigned boosted created ] + + include Assignments + + belongs_to :creator, class_name: "User" + belongs_to :bubble + + scope :threadable, -> { where action: THREADABLE_ACTIONS } +end diff --git a/app/models/event/assignments.rb b/app/models/event/assignments.rb new file mode 100644 index 000000000..ade131d31 --- /dev/null +++ b/app/models/event/assignments.rb @@ -0,0 +1,16 @@ +module Event::Assignments + extend ActiveSupport::Concern + + included do + store_accessor :particulars, :assignee_ids + end + + def assignee_names + assignees.map &:name + end + + private + def assignees + @assignees ||= creator.account.users.find assignee_ids + end +end diff --git a/app/views/boosts/_boosts.html.erb b/app/views/boosts/_boosts.html.erb index 1e2b1998b..973107476 100644 --- a/app/views/boosts/_boosts.html.erb +++ b/app/views/boosts/_boosts.html.erb @@ -1,6 +1,6 @@ <%= button_to bucket_bubble_boosts_path(bubble.bucket, bubble), - class: "btn btn--plain", - data: { action: "toggle-class#toggle" }, - form_class: "full-width" do %> - + <%= bubble.boosts.size if bubble.boosts.any? %> + class: "btn btn--plain", + data: { action: "toggle-class#toggle" }, + form_class: "full-width" do %> + + <%= bubble.boost_count if bubble.boost_count.positive? %> <% end %> diff --git a/app/views/boosts/create.turbo_stream.erb b/app/views/boosts/create.turbo_stream.erb index 4481aa4bf..718c9cf0d 100644 --- a/app/views/boosts/create.turbo_stream.erb +++ b/app/views/boosts/create.turbo_stream.erb @@ -1,3 +1,7 @@ <%= turbo_stream.update dom_id(@bubble, :boosts) do %> <%= render "boosts/boosts", bubble: @bubble %> <% end %> + +<%= turbo_stream.replace dom_id(@bubble, :thread) do %> + <%= render @bubble.thread %> +<% end %> diff --git a/app/views/bubbles/show.html.erb b/app/views/bubbles/show.html.erb index ee9cfbad8..9d320c4f7 100644 --- a/app/views/bubbles/show.html.erb +++ b/app/views/bubbles/show.html.erb @@ -25,7 +25,4 @@ <%= render "bubbles/bubble", bubble: @bubble %> -
- <%= render_comments_and_boosts(@bubble) %> - <%= render "comments/new", bubble: @bubble %> -
+<%= render @bubble.thread %> diff --git a/app/views/bubbles/threads/_rollup.html.erb b/app/views/bubbles/threads/_rollup.html.erb new file mode 100644 index 000000000..adbfd5138 --- /dev/null +++ b/app/views/bubbles/threads/_rollup.html.erb @@ -0,0 +1,3 @@ +
+ <%= rollup.body %> +
diff --git a/app/views/bubbles/threads/_thread.html.erb b/app/views/bubbles/threads/_thread.html.erb new file mode 100644 index 000000000..9d8103bb1 --- /dev/null +++ b/app/views/bubbles/threads/_thread.html.erb @@ -0,0 +1,6 @@ +<% bubble = thread.bubble %> + +
+ <%= render thread.entries %> + <%= render "comments/new", bubble: bubble %> +
diff --git a/app/views/buckets/index.html.erb b/app/views/buckets/index.html.erb index 08573bd09..8436c5658 100644 --- a/app/views/buckets/index.html.erb +++ b/app/views/buckets/index.html.erb @@ -20,7 +20,7 @@ <%= link_to bucket_bubbles_path(bucket), class: "windshield__container flex justify-center align-center position-relative" do %>
- <% bucket.bubbles.not_popped.left_joins(:comments, :boosts).group(:id).order(Arel.sql("COUNT(comments.id) + COUNT(boosts.id) DESC")).limit(10).each do |bubble| %> + <% bucket.bubbles.not_popped.ordered_by_activity.limit(10).each do |bubble| %>
diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb index 6d5892457..0cd298ab3 100644 --- a/app/views/comments/_comment.html.erb +++ b/app/views/comments/_comment.html.erb @@ -1,4 +1,4 @@ -
" id="<%= dom_id(comment) %>"> +<%= tag.div id: dom_id(comment), class: [ "comment flex align-start full-width", { "comment--mine": Current.user == comment.creator } ] do %>
<%= avatar_tag comment.creator, loading: :lazy %>
@@ -16,4 +16,4 @@ <%= simple_format comment.body %>
-
+<% end %> diff --git a/config/brakeman.ignore b/config/brakeman.ignore index add1499e7..28a750e32 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -22,8 +22,42 @@ 89 ], "note": "" + }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "344f91887bfa44bd0a64504b78280b988ab09d000518dafc8169215cdbc2b7b2", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/bubbles/show.html.erb", + "line": 28, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => @bucket.bubbles.find(params[:id]).thread, {})", + "render_path": [ + { + "type": "controller", + "class": "BubblesController", + "method": "show", + "line": 35, + "file": "app/controllers/bubbles_controller.rb", + "rendered": { + "name": "bubbles/show", + "file": "app/views/bubbles/show.html.erb" + } + } + ], + "location": { + "type": "template", + "template": "bubbles/show" + }, + "user_input": "params[:id]", + "confidence": "Weak", + "cwe_id": [ + 22 + ], + "note": "" } ], - "updated": "2024-10-03 15:28:53 -0400", + "updated": "2024-10-11 20:28:34 -0500", "brakeman_version": "6.2.1" } diff --git a/db/migrate/20241009181806_add_unique_index_to_assignments.rb b/db/migrate/20241009181806_add_unique_index_to_assignments.rb new file mode 100644 index 000000000..6600bef57 --- /dev/null +++ b/db/migrate/20241009181806_add_unique_index_to_assignments.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexToAssignments < ActiveRecord::Migration[8.0] + def change + add_index :assignments, %i[ assignee_id bubble_id ], unique: true + end +end diff --git a/db/migrate/20241009182303_make_comment_bodies_not_null.rb b/db/migrate/20241009182303_make_comment_bodies_not_null.rb new file mode 100644 index 000000000..eebedd368 --- /dev/null +++ b/db/migrate/20241009182303_make_comment_bodies_not_null.rb @@ -0,0 +1,5 @@ +class MakeCommentBodiesNotNull < ActiveRecord::Migration[8.0] + def change + change_column_null :comments, :body, false + end +end diff --git a/db/migrate/20241009183354_create_events.rb b/db/migrate/20241009183354_create_events.rb new file mode 100644 index 000000000..19c0f66da --- /dev/null +++ b/db/migrate/20241009183354_create_events.rb @@ -0,0 +1,14 @@ +class CreateEvents < ActiveRecord::Migration[8.0] + def change + create_table :events do |t| + t.references :bubble, null: false, index: false + t.references :creator, null: false + t.json :particulars, default: {} + t.string :action, null: false + + t.timestamps + end + + add_index :events, %i[ bubble_id action ], name: "index_events_on_bubble_id_and_action" + end +end diff --git a/db/migrate/20241009191253_drop_boosts.rb b/db/migrate/20241009191253_drop_boosts.rb new file mode 100644 index 000000000..9ecfeeea5 --- /dev/null +++ b/db/migrate/20241009191253_drop_boosts.rb @@ -0,0 +1,5 @@ +class DropBoosts < ActiveRecord::Migration[8.0] + def change + drop_table :boosts + end +end diff --git a/db/migrate/20241009191318_add_boost_count_to_bubbles.rb b/db/migrate/20241009191318_add_boost_count_to_bubbles.rb new file mode 100644 index 000000000..55a215ed5 --- /dev/null +++ b/db/migrate/20241009191318_add_boost_count_to_bubbles.rb @@ -0,0 +1,5 @@ +class AddBoostCountToBubbles < ActiveRecord::Migration[8.0] + def change + add_column :bubbles, :boost_count, :integer, null: false, default: 0 + end +end diff --git a/db/migrate/20241009211414_remove_assignments_index_on_assignee_id.rb b/db/migrate/20241009211414_remove_assignments_index_on_assignee_id.rb new file mode 100644 index 000000000..5f41bcc3a --- /dev/null +++ b/db/migrate/20241009211414_remove_assignments_index_on_assignee_id.rb @@ -0,0 +1,5 @@ +class RemoveAssignmentsIndexOnAssigneeId < ActiveRecord::Migration[8.0] + def change + remove_index :assignments, :assignee_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 4724af881..0218e49b4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2024_10_02_163242) do +ActiveRecord::Schema[8.0].define(version: 2024_10_09_211414) do create_table "accesses", force: :cascade do |t| t.integer "bucket_id", null: false t.integer "user_id", null: false @@ -64,18 +64,10 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_02_163242) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "assigner_id", null: false - t.index ["assignee_id"], name: "index_assignments_on_assignee_id" + t.index ["assignee_id", "bubble_id"], name: "index_assignments_on_assignee_id_and_bubble_id", unique: true t.index ["bubble_id"], name: "index_assignments_on_bubble_id" end - create_table "boosts", force: :cascade do |t| - t.integer "creator_id", null: false - t.integer "bubble_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["bubble_id"], name: "index_boosts_on_bubble_id" - end - create_table "bubbles", force: :cascade do |t| t.string "title" t.string "color" @@ -84,6 +76,7 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_02_163242) do t.integer "creator_id", null: false t.date "due_on" t.integer "bucket_id", null: false + t.integer "boost_count", default: 0, null: false t.index ["bucket_id"], name: "index_bubbles_on_bucket_id" end @@ -98,13 +91,24 @@ ActiveRecord::Schema[8.0].define(version: 2024_10_02_163242) do end create_table "comments", force: :cascade do |t| - t.text "body" + t.text "body", null: false t.integer "creator_id", null: false t.integer "bubble_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end + create_table "events", force: :cascade do |t| + t.integer "bubble_id", null: false + t.integer "creator_id", null: false + t.json "particulars", default: {} + t.string "action", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["bubble_id", "action"], name: "index_events_on_bubble_id_and_action" + t.index ["creator_id"], name: "index_events_on_creator_id" + end + create_table "pops", force: :cascade do |t| t.integer "bubble_id", null: false t.integer "user_id" diff --git a/test/controllers/.keep b/test/controllers/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/test/controllers/boosts_controller_test.rb b/test/controllers/boosts_controller_test.rb index 051cf6298..ce8135c78 100644 --- a/test/controllers/boosts_controller_test.rb +++ b/test/controllers/boosts_controller_test.rb @@ -1,7 +1,15 @@ require "test_helper" class BoostsControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + setup do + sign_in_as :kevin + end + + test "create" do + assert_difference "bubbles(:logo).reload.boost_count", +1 do + post bucket_bubble_boosts_url(buckets(:writebook), bubbles(:logo), format: :turbo_stream) + end + + assert_turbo_stream action: :update, target: dom_id(bubbles(:logo), :boosts) + end end diff --git a/test/controllers/comments_controller_test.rb b/test/controllers/comments_controller_test.rb index 3f25e015e..9578a2ffb 100644 --- a/test/controllers/comments_controller_test.rb +++ b/test/controllers/comments_controller_test.rb @@ -1,7 +1,15 @@ require "test_helper" class CommentsControllerTest < ActionDispatch::IntegrationTest - # test "the truth" do - # assert true - # end + setup do + sign_in_as :kevin + end + + test "create" do + assert_difference "bubbles(:logo).comments.count", +1 do + post bucket_bubble_comments_url(buckets(:writebook), bubbles(:logo), params: { comment: { body: "Agreed." } }) + end + + assert_redirected_to bucket_bubble_url(buckets(:writebook), bubbles(:logo)) + end end diff --git a/test/fixtures/assignments.yml b/test/fixtures/assignments.yml index d7dd9e0a6..6ac04e823 100644 --- a/test/fixtures/assignments.yml +++ b/test/fixtures/assignments.yml @@ -1,7 +1,14 @@ +logo_jz: + assigner: david + assignee: jz + bubble: logo + created_at: <%= 1.week.ago %> + logo_kevin: assigner: david assignee: kevin bubble: logo + created_at: <%= 1.day.ago %> layout_jz: assigner: david diff --git a/test/fixtures/boosts.yml b/test/fixtures/boosts.yml deleted file mode 100644 index 5d46d0cce..000000000 --- a/test/fixtures/boosts.yml +++ /dev/null @@ -1,15 +0,0 @@ -logo_jz_one: - bubble: logo - creator: jz - -logo_jz_two: - bubble: logo - creator: jz - -logo_kevin: - bubble: logo - creator: kevin - -layout_jz: - bubble: layout - creator: jz diff --git a/test/fixtures/bubbles.yml b/test/fixtures/bubbles.yml index 23ad32219..7e44c8a85 100644 --- a/test/fixtures/bubbles.yml +++ b/test/fixtures/bubbles.yml @@ -4,21 +4,26 @@ logo: title: The logo isn't big enough color: "#ED8008" due_on: <%= 3.days.from_now %> + created_at: <%= 1.week.ago %> + boost_count: 5 layout: bucket: writebook creator: david title: Layout is broken color: "#698F9C" + created_at: <%= 1.week.ago %> text: bucket: writebook creator: kevin title: The text is too small color: "#3B4B59" + created_at: <%= 1.week.ago %> shipping: bucket: writebook creator: kevin title: We need to ship the app color: "#ED8008" + created_at: <%= 1.week.ago %> diff --git a/test/fixtures/comments.yml b/test/fixtures/comments.yml index 0444a5a43..10ac4cca7 100644 --- a/test/fixtures/comments.yml +++ b/test/fixtures/comments.yml @@ -2,6 +2,7 @@ logo_agreement_jz: body: I agree. creator: jz bubble: logo + created_at: <%= 2.days.ago %> logo_agreement_kevin: body: Same, let’s do it. diff --git a/test/fixtures/events.yml b/test/fixtures/events.yml new file mode 100644 index 000000000..61da40243 --- /dev/null +++ b/test/fixtures/events.yml @@ -0,0 +1,72 @@ +logo_created: + creator: david + bubble: logo + action: created + created_at: <%= 1.week.ago %> + +logo_assignment_jz: + creator: david + bubble: logo + action: assigned + particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %> + created_at: <%= 1.week.ago %> + +logo_assignment_km: + creator: david + bubble: logo + action: assigned + particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:kevin) ] }.to_json %> + created_at: <%= 1.day.ago %> + +logo_boost_dhh: + creator: david + bubble: logo + action: boosted + created_at: <%= 1.week.ago %> + +logo_boost_km1: + creator: kevin + bubble: logo + action: boosted + created_at: <%= 1.day.ago %> + +logo_boost_km2: + creator: kevin + bubble: logo + action: boosted + created_at: <%= 1.day.ago %> + +logo_boost_jz1: + creator: jz + bubble: logo + action: boosted + created_at: <%= 1.day.ago %> + +logo_boost_jz2: + creator: jz + bubble: logo + action: boosted + +layout_created: + creator: david + bubble: layout + action: created + created_at: <%= 1.week.ago %> + +layout_assignment_jz: + creator: david + bubble: layout + action: assigned + particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %> + +text_created: + creator: kevin + bubble: text + action: created + created_at: <%= 1.week.ago %> + +shipping_created: + creator: kevin + bubble: shipping + action: created + created_at: <%= 1.week.ago %> diff --git a/test/models/bubble_test.rb b/test/models/bubble_test.rb index d22683df2..7e8447a38 100644 --- a/test/models/bubble_test.rb +++ b/test/models/bubble_test.rb @@ -1,6 +1,31 @@ require "test_helper" class BubbleTest < ActiveSupport::TestCase + setup do + Current.user = users(:kevin) + end + + test "boosting" do + assert_difference %w[ bubbles(:logo).boost_count bubbles(:logo).events.count ], +1 do + bubbles(:logo).boost! + end + end + + test "commenting" do + bubbles(:logo).comment! "Agreed." + + assert_equal "Agreed.", bubbles(:logo).comments.last.body + end + + test "assigning" do + bubbles(:logo).assign users(:david) + + assert_equal users(:kevin, :jz, :david), bubbles(:logo).assignees + assert_equal users(:david, :kevin), bubbles(:logo).assigners.uniq + assert_equal [ users(:david).id ], bubbles(:logo).events.last.assignee_ids + assert_equal [ "David" ], bubbles(:logo).events.last.assignee_names + end + test "searchable by title" do bubble = buckets(:writebook).bubbles.create! title: "Insufficient haggis", creator: users(:kevin)