@@ -0,0 +1,84 @@
|
||||
.notification-tray {
|
||||
height: 4rem;
|
||||
inset: auto auto var(--block-space);
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.notification {
|
||||
inset: auto auto 0;
|
||||
position: absolute;
|
||||
transform: translate(var(--offsetX), var(--offsetY));
|
||||
transform-origin: center center;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
z-index: var(--z-index);
|
||||
|
||||
.notification__content {
|
||||
color: var(--color-ink);
|
||||
inline-size: var(--width);
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
--offsetX: 0;
|
||||
--offsetY: 0;
|
||||
--width: 40ch;
|
||||
--z-index: 0;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
--offsetX: 0.5ch;
|
||||
--offsetY: calc(var(--block-space-half) * -1);
|
||||
--width: 39ch;
|
||||
--z-index: -1;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
--offsetX: 1ch;
|
||||
--offsetY: calc(var(--block-space) * -1);
|
||||
--width: 38ch;
|
||||
--z-index: -2;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
--offsetX: 1.5ch;
|
||||
--offsetY: calc(var(--block-space) * -1.5);
|
||||
--width: 37ch;
|
||||
--z-index: -3;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
--offsetX: 2ch;
|
||||
--offsetY: calc(var(--block-space) * -2);
|
||||
--width: 36ch;
|
||||
--z-index: -4;
|
||||
}
|
||||
|
||||
&:nth-child(1n + 6) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.notification-tray:hover & {
|
||||
&:nth-child(2) {
|
||||
--offsetX: 0;
|
||||
--offsetY: calc((100% + var(--block-space-half)) * -1);
|
||||
--width: 40ch;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
--offsetX: 0;
|
||||
--offsetY: calc((100% + var(--block-space-half)) * -2);
|
||||
--width: 40ch;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
--offsetX: 0;
|
||||
--offsetY: calc((100% + var(--block-space-half)) * -3);
|
||||
--width: 40ch;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
--offsetX: 0;
|
||||
--offsetY: calc((100% + var(--block-space-half)) * -4);
|
||||
--width: 40ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class NotificationsController < ApplicationController
|
||||
def index
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(20)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class ReadingsController < ApplicationController
|
||||
include BubbleScoped, BucketScoped
|
||||
|
||||
def create
|
||||
mark_bubble_notifications_read
|
||||
@notifications = Current.user.notifications.unread.ordered.limit(20)
|
||||
end
|
||||
|
||||
private
|
||||
def mark_bubble_notifications_read
|
||||
Current.user.notifications.where(bubble: @bubble).update(read: true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
module NotificationsHelper
|
||||
def notification_title(notification)
|
||||
title = notification.bubble.title
|
||||
|
||||
if notification.resource.is_a? Comment
|
||||
"RE: " + title
|
||||
else
|
||||
title
|
||||
end
|
||||
end
|
||||
|
||||
def notification_body(notification)
|
||||
name = notification.creator.name
|
||||
|
||||
case notification.event.action
|
||||
when "assigned" then "#{name} assigned to you"
|
||||
when "created" then "Added by #{name}"
|
||||
when "popped" then "Popped by by #{name}"
|
||||
else name
|
||||
end
|
||||
end
|
||||
|
||||
def notification_tag(notification, &)
|
||||
link_to notification.resource, id: dom_id(notification), class: "notification border-radius",
|
||||
data: { turbo_frame: "_top" }, &
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
import { post } from "@rails/request.js"
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { url: String }
|
||||
|
||||
connect() {
|
||||
post(this.urlValue, { responseKind: "turbo-stream" })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class GenerateNotificationsJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(event)
|
||||
event.generate_notifications
|
||||
end
|
||||
end
|
||||
@@ -1,9 +1,11 @@
|
||||
class Bubble < ApplicationRecord
|
||||
include Assignable, Boostable, Colored, Commentable, Eventable, Messages, Poppable, Searchable, Staged, Taggable
|
||||
include Assignable, Boostable, Colored, Commentable, Eventable, Messages, Notifiable, Poppable, Searchable, Staged, Taggable
|
||||
|
||||
belongs_to :bucket, touch: true
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
has_many :notifications, dependent: :destroy
|
||||
|
||||
has_one_attached :image, dependent: :purge_later
|
||||
|
||||
before_save :set_default_title
|
||||
|
||||
@@ -5,8 +5,9 @@ module Bubble::Commentable
|
||||
scope :ordered_by_comments, -> { order comments_count: :desc }
|
||||
end
|
||||
|
||||
def comment_created
|
||||
def comment_created(comment)
|
||||
increment! :comments_count
|
||||
track_event :commented, comment_id: comment.id
|
||||
rescore
|
||||
end
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ module Bubble::Eventable
|
||||
|
||||
private
|
||||
def track_event(action, creator: Current.user, **particulars)
|
||||
find_or_capture_event_summary.events.create! action: action, creator: creator, particulars: particulars
|
||||
event = find_or_capture_event_summary.events.create! action: action, creator: creator, particulars: particulars
|
||||
event.generate_notifications_later
|
||||
end
|
||||
|
||||
def find_or_capture_event_summary
|
||||
|
||||
@@ -13,7 +13,10 @@ module Bubble::Poppable
|
||||
end
|
||||
|
||||
def pop!(user: Current.user)
|
||||
create_pop!(user: user) unless popped?
|
||||
unless popped?
|
||||
create_pop!(user: user)
|
||||
track_event :popped
|
||||
end
|
||||
end
|
||||
|
||||
def unpop
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Comment < ApplicationRecord
|
||||
include Searchable, Messageable
|
||||
include Messageable, Notifiable, Searchable
|
||||
|
||||
belongs_to :creator, class_name: "User", default: -> { Current.user }
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module Notifiable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :notifications, as: :resource, dependent: :destroy
|
||||
end
|
||||
end
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
class Event < ApplicationRecord
|
||||
include Assignments, Stages
|
||||
include Particulars
|
||||
|
||||
belongs_to :creator, class_name: "User"
|
||||
belongs_to :summary, touch: true, class_name: "EventSummary"
|
||||
@@ -9,4 +9,12 @@ class Event < ApplicationRecord
|
||||
scope :chronologically, -> { order created_at: :asc, id: :desc }
|
||||
scope :non_boosts, -> { where.not action: :boosted }
|
||||
scope :boosts, -> { where action: :boosted }
|
||||
|
||||
def generate_notifications
|
||||
Notifier.for(self)&.generate
|
||||
end
|
||||
|
||||
def generate_notifications_later
|
||||
GenerateNotificationsJob.perform_later self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module Event::Assignments
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :particulars, :assignee_ids
|
||||
end
|
||||
|
||||
def assignees
|
||||
@assignees ||= account.users.where id: assignee_ids
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
module Event::Particulars
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :particulars, :assignee_ids, :comment_id, :stage_id, :stage_name
|
||||
end
|
||||
|
||||
def assignees
|
||||
@assignees ||= account.users.where id: assignee_ids
|
||||
end
|
||||
|
||||
def comment
|
||||
@comment ||= Comment.find(comment_id)
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
module Event::Stages
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
store_accessor :particulars, :stage_id, :stage_name
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@ class Message < ApplicationRecord
|
||||
|
||||
private
|
||||
def created
|
||||
bubble.comment_created if comment?
|
||||
bubble.comment_created(comment) if comment?
|
||||
end
|
||||
|
||||
def destroyed
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class Notification < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :event
|
||||
belongs_to :bubble
|
||||
belongs_to :resource, polymorphic: true
|
||||
|
||||
scope :unread, -> { where(read: false) }
|
||||
scope :ordered, -> { order(read: :desc, created_at: :desc) }
|
||||
|
||||
delegate :creator, to: :event
|
||||
|
||||
broadcasts_to ->(notification) { [ notification.user, :notifications ] }, inserts_by: :prepend
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
class Notifier
|
||||
attr_reader :event
|
||||
|
||||
delegate :creator, to: :event
|
||||
|
||||
class << self
|
||||
def for(event)
|
||||
"Notifier::#{event.action.classify}".safe_constantize&.new(event)
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
recipients.map do |recipient|
|
||||
Notification.create! user: recipient, event: event, bubble: bubble, resource: resource
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def initialize(event)
|
||||
@event = event
|
||||
end
|
||||
|
||||
def recipients
|
||||
bubble.bucket.users.without(creator)
|
||||
end
|
||||
|
||||
def resource
|
||||
bubble
|
||||
end
|
||||
|
||||
def bubble
|
||||
event.summary.message.bubble
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class Notifier::Assigned < Notifier
|
||||
private
|
||||
def recipients
|
||||
event.assignees.without(creator)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class Notifier::Commented < Notifier
|
||||
private
|
||||
def resource
|
||||
event.comment
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
class Notifier::Created < Notifier
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
class Notifier::Popped < Notifier
|
||||
end
|
||||
@@ -18,6 +18,8 @@ class User < ApplicationRecord
|
||||
has_many :assignings, foreign_key: :assigner_id, class_name: "Assignment"
|
||||
has_many :assigned_bubbles, through: :assignments, source: :bubble
|
||||
|
||||
has_many :notifications, dependent: :destroy
|
||||
|
||||
has_one_attached :avatar
|
||||
|
||||
validates_presence_of :email_address
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div class="bubble"
|
||||
style="view-transition-name: bubble-<%= bubble.id -%>; --bubble-color: <%= bubble.color %>; <%= bubble_rotation(bubble) %> <%= bubble_size(bubble) %>"
|
||||
data-controller="animation upload-preview"
|
||||
data-animation-play-class="bubble--wobble" data-animation-play-on-load-value="true" data-action="mouseover->animation#play">
|
||||
data-animation-play-class="bubble--wobble"
|
||||
data-animation-play-on-load-value="true"
|
||||
data-action="mouseover->animation#play">
|
||||
|
||||
<div>
|
||||
<h1 class="bubble__title">
|
||||
|
||||
@@ -50,3 +50,5 @@
|
||||
<%= render partial: "bubbles/list/bubble", collection: @bubbles, cached: true %>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<%= render "notifications/tray" %>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="bubble__container">
|
||||
<div class="bubble__container" data-controller="beacon" data-beacon-url-value="<%= bucket_bubble_readings_url(@bubble.bucket, @bubble) %>">
|
||||
<div class="bubble__perma flex justify-center center margin-block-end-double">
|
||||
<%= render "bubbles/bubble", bubble: @bubble, editing: params[:editing] %>
|
||||
</div>
|
||||
@@ -76,3 +76,5 @@
|
||||
<div class="flex align-start justify-start">
|
||||
<%= turbo_frame_tag dom_id(@bubble, :stage_picker), src: new_bucket_bubble_stage_picker_path(@bubble.bucket, @bubble) %>
|
||||
</div>
|
||||
|
||||
<%= render "notifications/tray" %>
|
||||
|
||||
@@ -20,3 +20,5 @@
|
||||
<div class="buckets margin-block-double unpad align-start justify-center flex flex-wrap gap">
|
||||
<%= render @filters %>
|
||||
</div>
|
||||
|
||||
<%= render "notifications/tray" %>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<%= notification_tag notification do %>
|
||||
<div class="notification__content border-radius borderless pad shadow fill-white flex align-start txt-align-start gap-half">
|
||||
<div class="avatar txt-small">
|
||||
<%= avatar_image_tag(notification.creator) %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column txt-tight-lines">
|
||||
<strong><%= notification_title(notification) %></strong>
|
||||
<%= notification_body(notification) %> · <%= time_ago_in_words(notification.created_at) %> ago
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,5 @@
|
||||
<%= turbo_stream_from Current.user, :notifications %>
|
||||
|
||||
<%= tag.div id: "notification-tray", class: "notification-tray flex flex-column gap", data: { turbo_permanent: true } do %>
|
||||
<%= turbo_frame_tag("notifications", src: notifications_path) %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_frame_tag "notifications" do %>
|
||||
<%= render @notifications %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_stream.update :notifications do %>
|
||||
<%= render @notifications %>
|
||||
<% end %>
|
||||
@@ -4,6 +4,7 @@ pin "application"
|
||||
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
||||
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
||||
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
||||
pin "@rails/request.js", to: "@rails--request.js" # @0.0.11
|
||||
pin "house", to: "house.min.js"
|
||||
|
||||
pin_all_from "app/javascript/controllers", under: "controllers"
|
||||
|
||||
@@ -12,12 +12,19 @@ Rails.application.routes.draw do
|
||||
route_for :bucket_bubble, bubble.bucket, bubble, options
|
||||
end
|
||||
|
||||
resolve "Comment" do |comment, options|
|
||||
options[:anchor] = ActionView::RecordIdentifier.dom_id(comment)
|
||||
route_for :bucket_bubble, comment.bubble.bucket, comment.bubble, options
|
||||
end
|
||||
|
||||
resources :bubbles
|
||||
resources :notifications
|
||||
|
||||
resources :buckets do
|
||||
resources :bubbles do
|
||||
resources :boosts
|
||||
resources :comments
|
||||
resource :readings, only: :create
|
||||
|
||||
scope module: :bubbles do
|
||||
resource :image
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
class CreateNotifications < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
create_table :notifications do |t|
|
||||
t.references :user, null: false, foreign_key: true
|
||||
t.references :event, null: false, foreign_key: true
|
||||
t.references :bubble, null: false, foreign_key: true
|
||||
t.references :resource, null: false, polymorphic: true, index: true
|
||||
t.boolean :read, default: false, null: false
|
||||
|
||||
t.timestamps
|
||||
|
||||
t.index %i[ user_id read created_at ], order: { read: :desc, created_at: :desc }
|
||||
end
|
||||
end
|
||||
end
|
||||
Generated
+20
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2024_12_09_223551) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_01_09_153649) do
|
||||
create_table "accesses", force: :cascade do |t|
|
||||
t.integer "bucket_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
@@ -176,6 +176,22 @@ ActiveRecord::Schema[8.1].define(version: 2024_12_09_223551) do
|
||||
t.index ["messageable_type", "messageable_id"], name: "index_messages_on_messageable", unique: true
|
||||
end
|
||||
|
||||
create_table "notifications", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "event_id", null: false
|
||||
t.integer "bubble_id", null: false
|
||||
t.string "resource_type", null: false
|
||||
t.integer "resource_id", null: false
|
||||
t.boolean "read", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["bubble_id"], name: "index_notifications_on_bubble_id"
|
||||
t.index ["event_id"], name: "index_notifications_on_event_id"
|
||||
t.index ["resource_type", "resource_id"], name: "index_notifications_on_resource"
|
||||
t.index ["user_id", "read", "created_at"], name: "index_notifications_on_user_id_and_read_and_created_at", order: { read: :desc, created_at: :desc }
|
||||
t.index ["user_id"], name: "index_notifications_on_user_id"
|
||||
end
|
||||
|
||||
create_table "pops", force: :cascade do |t|
|
||||
t.integer "bubble_id", null: false
|
||||
t.integer "user_id"
|
||||
@@ -244,6 +260,9 @@ ActiveRecord::Schema[8.1].define(version: 2024_12_09_223551) do
|
||||
add_foreign_key "bubbles", "workflow_stages", column: "stage_id"
|
||||
add_foreign_key "events", "event_summaries", column: "summary_id"
|
||||
add_foreign_key "messages", "bubbles"
|
||||
add_foreign_key "notifications", "bubbles"
|
||||
add_foreign_key "notifications", "events"
|
||||
add_foreign_key "notifications", "users"
|
||||
add_foreign_key "pops", "bubbles"
|
||||
add_foreign_key "pops", "users"
|
||||
add_foreign_key "sessions", "users"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
require "test_helper"
|
||||
|
||||
class NotificationsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "index" do
|
||||
get notifications_url
|
||||
|
||||
assert_response :success
|
||||
assert_select "div", text: /Layout is broken/
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class ReadingsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in_as :kevin
|
||||
end
|
||||
|
||||
test "index" do
|
||||
assert_changes -> { notifications(:logo_created_kevin).reload.read? }, from: false, to: true do
|
||||
post bucket_bubble_readings_url(bubbles(:logo).bucket, bubbles(:logo)), as: :turbo_stream
|
||||
end
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
end
|
||||
Vendored
+13
@@ -54,6 +54,13 @@ layout_created:
|
||||
summary: layout_initial_activity
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
layout_commented:
|
||||
creator: david
|
||||
action: commented
|
||||
summary: layout_initial_activity
|
||||
particulars: <%= { comment_id: ActiveRecord::FixtureSet.identify(:layout_overflowing_david) }.to_json %>
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
layout_assignment_jz:
|
||||
creator: david
|
||||
action: assigned
|
||||
@@ -72,3 +79,9 @@ shipping_created:
|
||||
action: created
|
||||
summary: shipping_initial_activity
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
shipping_popped:
|
||||
creator: kevin
|
||||
action: popped
|
||||
summary: shipping_initial_activity
|
||||
created_at: <%= 2.days.ago %>
|
||||
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
logo_created_kevin:
|
||||
user: kevin
|
||||
event: logo_created
|
||||
bubble: logo
|
||||
resource: logo (Bubble)
|
||||
read: false
|
||||
created_at: <%= 1.week.ago %>
|
||||
|
||||
layout_commented_kevin:
|
||||
user: kevin
|
||||
event: layout_commented
|
||||
bubble: layout
|
||||
resource: layout_overflowing_david (Comment)
|
||||
read: false
|
||||
created_at: <%= 1.week.ago %>
|
||||
@@ -9,7 +9,9 @@ class Bubble::PoppableTest < ActiveSupport::TestCase
|
||||
test "popping" do
|
||||
assert_not bubbles(:logo).popped?
|
||||
|
||||
bubbles(:logo).pop!
|
||||
with_current_user(:kevin) do
|
||||
bubbles(:logo).pop!
|
||||
end
|
||||
|
||||
assert bubbles(:logo).popped?
|
||||
end
|
||||
|
||||
@@ -6,11 +6,11 @@ class BubbleTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "capturing messages" do
|
||||
assert_difference "bubbles(:logo).messages.count", +1 do
|
||||
assert_difference "bubbles(:logo).messages.comments.count", +1 do
|
||||
bubbles(:logo).capture Comment.new(body: "Agreed.")
|
||||
end
|
||||
|
||||
assert_equal "Agreed.", bubbles(:logo).messages.last.messageable.body.to_plain_text.chomp
|
||||
assert_equal "Agreed.", bubbles(:logo).messages.comments.last.messageable.body.to_plain_text.chomp
|
||||
end
|
||||
|
||||
test "boosting" do
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
require "test_helper"
|
||||
|
||||
class Notifier::AssignedTest < ActiveSupport::TestCase
|
||||
test "creates a notification for the assignee" do
|
||||
notifications = Notifier.for(events(:logo_assignment_km)).generate
|
||||
|
||||
assert_equal [ users(:kevin) ], notifications.map(&:user)
|
||||
end
|
||||
|
||||
test "does not notify for self-assignments" do
|
||||
event = EventSummary.last.events.create! action: :assigned,
|
||||
creator: users(:kevin),
|
||||
particulars: { assignee_ids: [ users(:kevin).id ] }
|
||||
|
||||
assert_no_difference -> { Notification.count } do
|
||||
Notifier.for(event).generate
|
||||
end
|
||||
end
|
||||
|
||||
test "links to the bubble" do
|
||||
Notifier.for(events(:logo_assignment_km)).generate
|
||||
|
||||
assert_equal bubbles(:logo), Notification.last.resource
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class Notifier::CommentedTest < ActiveSupport::TestCase
|
||||
test "creates a notification for each recipient" do
|
||||
notifications = Notifier.for(events(:layout_commented)).generate
|
||||
|
||||
assert_equal users(:kevin, :jz).sort, notifications.map(&:user).sort
|
||||
end
|
||||
|
||||
test "links to the bubble" do
|
||||
Notifier.for(events(:layout_commented)).generate
|
||||
|
||||
assert_equal comments(:layout_overflowing_david), Notification.last.resource
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class Notifier::CreatedTest < ActiveSupport::TestCase
|
||||
test "creates a notification for each recipient" do
|
||||
notifications = Notifier.for(events(:logo_created)).generate
|
||||
|
||||
assert_equal users(:kevin, :jz).sort, notifications.map(&:user).sort
|
||||
end
|
||||
|
||||
test "links to the bubble" do
|
||||
Notifier.for(events(:logo_created)).generate
|
||||
|
||||
assert_equal bubbles(:logo), Notification.last.resource
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class Notifier::PoppedTest < ActiveSupport::TestCase
|
||||
test "creates a notification for each recipient" do
|
||||
notifications = Notifier.for(events(:shipping_popped)).generate
|
||||
|
||||
assert_equal users(:david, :jz).sort, notifications.map(&:user).sort
|
||||
end
|
||||
|
||||
test "links to the bubble" do
|
||||
Notifier.for(events(:shipping_popped)).generate
|
||||
|
||||
assert_equal bubbles(:shipping), Notification.last.resource
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require "test_helper"
|
||||
|
||||
class NotifierTest < ActiveSupport::TestCase
|
||||
test "for returns the matching notifier class for the event" do
|
||||
assert_kind_of Notifier::Created, Notifier.for(events(:logo_created))
|
||||
end
|
||||
|
||||
test "for does not raise an error when the event is not notifiable" do
|
||||
assert_nothing_raised do
|
||||
assert_no_difference -> { Notification.count } do
|
||||
Notifier.for(events(:logo_boost_dhh))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,4 +13,12 @@ module SessionTestHelper
|
||||
delete session_url
|
||||
assert_not cookies[:session_token].present?
|
||||
end
|
||||
|
||||
def with_current_user(user)
|
||||
user = users(user) unless user.is_a? User
|
||||
Current.session = Session.new(user: user)
|
||||
yield
|
||||
ensure
|
||||
Current.reset_all
|
||||
end
|
||||
end
|
||||
|
||||
Vendored
+4
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user