Merge pull request #2549 from consul/read_notifications

Extend notifications to be marked as read or unread
This commit is contained in:
Raimond Garcia
2018-03-23 22:30:23 +01:00
committed by GitHub
26 changed files with 529 additions and 155 deletions

View File

@@ -174,6 +174,13 @@ a {
margin-right: $line-height; margin-right: $line-height;
} }
.float-right-medium {
@include breakpoint(medium) {
float: right !important;
}
}
.no-margin-top { .no-margin-top {
margin-top: rem-calc(-24); margin-top: rem-calc(-24);
} }
@@ -1169,21 +1176,46 @@ form {
} }
} }
.notifications-list::before { .notifications-list {
position: relative;
&::before {
background: $border; background: $border;
content: ''; content: '';
height: 100%; height: 100%;
left: 22px; left: 7px;
position: absolute; position: absolute;
top: 60px; top: 2px;
width: 2px; width: 2px;
}
} }
.notification { .notification {
border: 1px solid $border;
display: block; display: block;
padding: $line-height / 2 0 $line-height / 2 $line-height * 1.5; margin-bottom: $line-height / 4;
margin-left: $line-height;
padding: $line-height / 2 $line-height;
position: relative; position: relative;
@include breakpoint(medium) {
.mark-notification {
position: absolute;
right: 12px;
top: 12px;
}
}
&.unread {
background: $highlight-soft;
border: 1px solid $highlight;
&:hover {
background: $highlight;
}
}
&:hover { &:hover {
a { a {
@@ -1204,13 +1236,19 @@ form {
color: $brand; color: $brand;
content: '\4d'; content: '\4d';
font-family: "icons" !important; font-family: "icons" !important;
left: 0; font-size: $small-font-size;
height: rem-calc(20);
left: -24px;
line-height: rem-calc(20);
position: absolute; position: absolute;
text-align: center;
width: rem-calc(20);
} }
p { p {
color: $text; color: $text;
margin-bottom: 0; margin-bottom: 0;
max-width: 80%;
} }
.time { .time {

View File

@@ -86,7 +86,7 @@ class CommentsController < ApplicationController
notifiable = comment.reply? ? comment.parent : comment.commentable notifiable = comment.reply? ? comment.parent : comment.commentable
notifiable_author_id = notifiable.try(:author_id) notifiable_author_id = notifiable.try(:author_id)
if notifiable_author_id.present? && notifiable_author_id != comment.author_id if notifiable_author_id.present? && notifiable_author_id != comment.author_id
Notification.add(notifiable.author_id, notifiable) Notification.add(notifiable.author, notifiable)
end end
end end

View File

@@ -2,29 +2,41 @@ class NotificationsController < ApplicationController
include CustomUrlsHelper include CustomUrlsHelper
before_action :authenticate_user! before_action :authenticate_user!
after_action :mark_as_read, only: :show
skip_authorization_check skip_authorization_check
respond_to :html, :js
def index def index
@notifications = current_user.notifications.unread.recent.for_render @notifications = current_user.notifications.unread
end end
def show def show
@notification = current_user.notifications.find(params[:id]) @notification = current_user.notifications.find(params[:id])
@notification.mark_as_read
redirect_to linkable_resource_path(@notification) redirect_to linkable_resource_path(@notification)
end end
def read
@notifications = current_user.notifications.read
end
def mark_all_as_read def mark_all_as_read
current_user.notifications.each { |notification| notification.mark_as_read } current_user.notifications.unread.each { |notification| notification.mark_as_read }
redirect_to notifications_path redirect_to notifications_path
end end
private
def mark_as_read def mark_as_read
@notification = current_user.notifications.find(params[:id])
@notification.mark_as_read @notification.mark_as_read
end end
def mark_as_unread
@notification = current_user.notifications.find(params[:id])
@notification.mark_as_unread
end
private
def linkable_resource_path(notification) def linkable_resource_path(notification)
case notification.linkable_resource.class.name case notification.linkable_resource.class.name
when "Budget::Investment" when "Budget::Investment"

View File

@@ -12,7 +12,7 @@ class ProposalNotificationsController < ApplicationController
@proposal = Proposal.find(proposal_notification_params[:proposal_id]) @proposal = Proposal.find(proposal_notification_params[:proposal_id])
if @notification.save if @notification.save
@proposal.users_to_notify.each do |user| @proposal.users_to_notify.each do |user|
Notification.add(user.id, @notification) Notification.add(user, @notification)
end end
redirect_to @notification, notice: I18n.t("flash.actions.create.proposal_notification") redirect_to @notification, notice: I18n.t("flash.actions.create.proposal_notification")
else else

View File

@@ -3,32 +3,50 @@ class Notification < ActiveRecord::Base
belongs_to :user, counter_cache: true belongs_to :user, counter_cache: true
belongs_to :notifiable, polymorphic: true belongs_to :notifiable, polymorphic: true
scope :unread, -> { all } validates :user, presence: true
scope :recent, -> { order(id: :desc) }
scope :read, -> { where.not(read_at: nil).recent.for_render }
scope :unread, -> { where(read_at: nil).recent.for_render }
scope :not_emailed, -> { where(emailed_at: nil) } scope :not_emailed, -> { where(emailed_at: nil) }
scope :recent, -> { order(id: :desc) }
scope :for_render, -> { includes(:notifiable) } scope :for_render, -> { includes(:notifiable) }
delegate :notifiable_title, :notifiable_available?, :check_availability, :linkable_resource, delegate :notifiable_title, :notifiable_available?, :check_availability,
to: :notifiable, allow_nil: true :linkable_resource, to: :notifiable, allow_nil: true
def mark_as_read
update(read_at: Time.current)
end
def mark_as_unread
update(read_at: nil)
end
def read?
read_at.present?
end
def unread?
read_at.nil?
end
def timestamp def timestamp
notifiable.created_at notifiable.created_at
end end
def mark_as_read def self.add(user, notifiable)
destroy notification = Notification.existent(user, notifiable)
end
def self.add(user_id, notifiable)
notification = Notification.find_by(user_id: user_id, notifiable: notifiable)
if notification.present? if notification.present?
Notification.increment_counter(:counter, notification.id) increment_counter(:counter, notification.id)
else else
Notification.create!(user_id: user_id, notifiable: notifiable) create!(user: user, notifiable: notifiable)
end end
end end
def self.existent(user, notifiable)
unread.where(user: user, notifiable: notifiable).first
end
def notifiable_action def notifiable_action
case notifiable_type case notifiable_type
when "ProposalNotification" when "ProposalNotification"

View File

@@ -1,22 +1,4 @@
<% if user_signed_in? %> <% if user_signed_in? %>
<li id="notifications">
<%= link_to notifications_path, rel: "nofollow", class: "notifications" do %>
<span class="show-for-sr"><%= t("layouts.header.notifications") %></span>
<% if current_user.notifications_count > 0 %>
<span class="icon-circle" aria-hidden="true"></span>
<span class="icon-notification" aria-hidden="true" title="<%= t('layouts.header.new_notifications', count: current_user.notifications_count).html_safe %>">
</span>
<small class="show-for-small-only">
<%= t('layouts.header.new_notifications', count: current_user.notifications_count).html_safe %>
</small>
<% else %>
<span class="icon-no-notification" aria-hidden="true" title="<%= t('layouts.header.no_notifications') %>"></span>
<small class="show-for-small-only">
<%= t('layouts.header.no_notifications') %>
</small>
<% end %>
<% end %>
</li>
<li> <li>
<%= layout_menu_link_to t("layouts.header.my_activity_link"), <%= layout_menu_link_to t("layouts.header.my_activity_link"),
user_path(current_user), user_path(current_user),

View File

@@ -28,6 +28,7 @@
<ul class="menu" data-responsive-menu="medium-dropdown"> <ul class="menu" data-responsive-menu="medium-dropdown">
<%= render "admin/shared/admin_shortcuts" %> <%= render "admin/shared/admin_shortcuts" %>
<%= render "shared/admin_login_items" %> <%= render "shared/admin_login_items" %>
<%= render "layouts/notification_item" %>
<%= render "devise/menu/login_items" %> <%= render "devise/menu/login_items" %>
</ul> </ul>
</div> </div>

View File

@@ -30,6 +30,7 @@
<div class="top-bar-right"> <div class="top-bar-right">
<ul class="menu" data-responsive-menu="medium-dropdown"> <ul class="menu" data-responsive-menu="medium-dropdown">
<%= render "shared/admin_login_items" %> <%= render "shared/admin_login_items" %>
<%= render "layouts/notification_item" %>
<%= render "devise/menu/login_items" %> <%= render "devise/menu/login_items" %>
</ul> </ul>

View File

@@ -0,0 +1,30 @@
<% if user_signed_in? %>
<li id="notifications">
<%= link_to notifications_path, rel: "nofollow",
class: "notifications" do %>
<span class="show-for-sr">
<%= t("layouts.header.notification_item.notifications") %>
</span>
<% if current_user.notifications.unread.count > 0 %>
<span class="icon-circle" aria-hidden="true"></span>
<span class="icon-notification" aria-hidden="true"
title="<%= t('layouts.header.notification_item.new_notifications',
count: current_user.notifications_count).html_safe %>">
</span>
<small class="show-for-small-only">
<%= t('layouts.header.notification_item.new_notifications',
count: current_user.notifications_count).html_safe %>
</small>
<% else %>
<span class="icon-no-notification" aria-hidden="true"
title="<%= t('layouts.header.notification_item.no_notifications') %>">
</span>
<small class="show-for-small-only">
<%= t('layouts.header.notification_item.no_notifications') %>
</small>
<% end %>
<% end %>
</li>
<% end %>

View File

@@ -1,21 +1,38 @@
<li id="<%= dom_id(notification) %>" class="notification"> <li id="<%= dom_id(notification) %>" class="notification <%= "unread" if notification.unread? %>">
<% if notification.notifiable_available? %> <% if notification.notifiable_available? %>
<%= link_to notification do %> <%= link_to notification do %>
<p> <p>
<em> <em>
<%= t("notifications.index.#{notification.notifiable_action}", <%= t("notifications.notification.action.#{notification.notifiable_action}",
count: notification.counter) %> count: notification.counter) %>
</em> </em>
<strong><%= notification.notifiable_title %></strong> <strong id="<%= dom_id(notification) %>_title">
<%= notification.notifiable_title %>
</strong>
</p> </p>
<p class="time"><%= l notification.timestamp, format: :datetime %></p> <p class="time">
<%= l notification.timestamp, format: :datetime %>
</p>
<% end %> <% end %>
<% else %> <% else %>
<p> <p>
<strong> <strong>
<%= t("notifications.index.notifiable_hidden") %> <%= t("notifications.notification.notifiable_hidden") %>
</strong> </strong>
</p> </p>
<% end %> <% end %>
<% if notification.unread? %>
<%= link_to t("notifications.notification.mark_as_read"),
mark_as_read_notification_path(notification),
method: :put, remote: true,
class: "mark-notification small" %>
<% else %>
<%= link_to t("notifications.notification.mark_as_unread"),
mark_as_unread_notification_path(notification),
method: :put, remote: true,
class: "mark-notification small" %>
<% end %>
</li> </li>

View File

@@ -1,18 +1,28 @@
<div class="row margin-bottom"> <div class="row margin-bottom">
<div class="small-12 column relative"> <div class="small-12 column">
<h1 class="float-left"><%= t("notifications.index.title") %></h1> <h1 class="inline-block margin-bottom">
<%= t("notifications.index.title") %>
</h1>
<%= link_to t("notifications.index.mark_all_as_read"),
mark_all_as_read_notifications_path, method: :put,
class: "button hollow float-right-medium" %>
<ul class="menu simple clear">
<li class="active">
<h2><%= link_to t("notifications.index.unread"), notifications_path %></h2>
</li>
<li>
<%= link_to t("notifications.index.read"), read_notifications_path %>
</li>
</ul>
<% if @notifications.empty? %> <% if @notifications.empty? %>
<div data-alert class="callout primary margin-top clear"> <div data-alert class="callout primary margin-top clear">
<%= t("notifications.index.empty_notifications") %> <%= t("notifications.index.empty_notifications") %>
</div> </div>
<% else %> <% else %>
<div class="float-right margin-top">
<%= link_to t("notifications.index.mark_all_as_read"),
mark_all_as_read_notifications_path, method: :put %>
</div>
<ul class="no-bullet clear notifications-list"> <ul class="no-bullet clear notifications-list">
<%= render @notifications %> <%= render @notifications %>
</ul> </ul>

View File

@@ -0,0 +1 @@
$("#notification_<%= @notification.id %>").hide()

View File

@@ -0,0 +1 @@
$("#notification_<%= @notification.id %>").hide()

View File

@@ -0,0 +1,19 @@
<div class="row margin-bottom">
<div class="small-12 column">
<h1><%= t("notifications.index.title") %></h1>
<ul class="menu simple clear">
<li>
<%= link_to t("notifications.index.unread"), notifications_path %>
</li>
<li class="active">
<h2><%= link_to t("notifications.index.read"), read_notifications_path %></h2>
</li>
</ul>
<ul class="no-bullet clear notifications-list">
<%= render @notifications %>
</ul>
</div>
</div>

View File

@@ -164,9 +164,7 @@ ignore_unused:
- 'proposals.index.section_header.*' - 'proposals.index.section_header.*'
- 'spending_proposals.index.search_form.*' - 'spending_proposals.index.search_form.*'
- '*.index.search_form.*' - '*.index.search_form.*'
- 'notifications.index.comments_on*' - 'notifications.notification.action.*'
- 'notifications.index.replies_to*'
- 'notifications.index.proposal_notification*'
- 'legislation.processes.index.filter*' - 'legislation.processes.index.filter*'
- 'legislation.processes.index.section_header.*' - 'legislation.processes.index.section_header.*'
- 'helpers.page_entries_info.*' # kaminari - 'helpers.page_entries_info.*' # kaminari

View File

@@ -234,11 +234,6 @@ en:
help: Help help: Help
my_account_link: My account my_account_link: My account
my_activity_link: My activity my_activity_link: My activity
notifications: Notifications
new_notifications:
one: You have a new notification
other: You have %{count} new notifications
no_notifications: "You don't have new notifications"
open: open open: open
open_city_slogan_html: There are cities that are governed directly by their inhabitants, who <b>discuss</b> the topics they are concerned about, <b>propose</b> ideas to improve their lives and <b>decide</b> among themselves which ones will be carried out. open_city_slogan_html: There are cities that are governed directly by their inhabitants, who <b>discuss</b> the topics they are concerned about, <b>propose</b> ideas to improve their lives and <b>decide</b> among themselves which ones will be carried out.
open_city_title: Love the city, and it will become a city you love open_city_title: Love the city, and it will become a city you love
@@ -247,6 +242,12 @@ en:
poll_questions: Voting poll_questions: Voting
budgets: Participatory budgeting budgets: Participatory budgeting
spending_proposals: Spending Proposals spending_proposals: Spending Proposals
notification_item:
new_notifications:
one: You have a new notification
other: You have %{count} new notifications
notifications: Notifications
no_notifications: "You don't have new notifications"
admin: admin:
watch_form_message: 'You have unsaved changes. Do you confirm to leave the page?' watch_form_message: 'You have unsaved changes. Do you confirm to leave the page?'
legacy_legislation: legacy_legislation:
@@ -259,19 +260,25 @@ en:
locale: English locale: English
notifications: notifications:
index: index:
empty_notifications: You don't have new notifications.
mark_all_as_read: Mark all as read
read: All notifications
title: Notifications
unread: Unread
notification:
action:
comments_on: comments_on:
one: Someone commented on one: Someone commented on
other: There are %{count} new comments on other: There are %{count} new comments on
empty_notifications: You don't have new notifications.
notifiable_hidden: This resource is not available anymore.
mark_all_as_read: Mark all as read
proposal_notification: proposal_notification:
one: There is one new notification on one: There is one new notification on
other: There are %{count} new notifications on other: There are %{count} new notifications on
replies_to: replies_to:
one: Someone replied to your comment on one: Someone replied to your comment on
other: There are %{count} new replies to your comment on other: There are %{count} new replies to your comment on
title: Notifications mark_as_read: Mark as read
mark_as_unread: Mark as unread
notifiable_hidden: This resource is not available anymore.
map: map:
title: "Districts" title: "Districts"
proposal_for_district: "Start a proposal for your district" proposal_for_district: "Start a proposal for your district"

View File

@@ -234,11 +234,6 @@ es:
help: Ayuda help: Ayuda
my_account_link: Mi cuenta my_account_link: Mi cuenta
my_activity_link: Mi actividad my_activity_link: Mi actividad
notifications: Notificaciones
new_notifications:
one: Tienes una nueva notificación
other: Tienes %{count} notificaciones nuevas
no_notifications: "No tienes notificaciones nuevas"
open: abierto open: abierto
open_city_slogan_html: Existen ciudades gobernadas directamente por sus habitantes, que <strong>debaten</strong> sobre temas que les preocupan, <strong>proponen</strong> ideas para mejorar sus vidas y <strong>deciden</strong> entre todas y todos las que se llevan a cabo. open_city_slogan_html: Existen ciudades gobernadas directamente por sus habitantes, que <strong>debaten</strong> sobre temas que les preocupan, <strong>proponen</strong> ideas para mejorar sus vidas y <strong>deciden</strong> entre todas y todos las que se llevan a cabo.
open_city_title: La ciudad que quieres será la ciudad que quieras open_city_title: La ciudad que quieres será la ciudad que quieras
@@ -247,6 +242,12 @@ es:
poll_questions: Votaciones poll_questions: Votaciones
budgets: Presupuestos participativos budgets: Presupuestos participativos
spending_proposals: Propuestas de inversión spending_proposals: Propuestas de inversión
notification_item:
new_notifications:
one: Tienes una nueva notificación
other: Tienes %{count} notificaciones nuevas
notifications: Notificaciones
no_notifications: "No tienes notificaciones nuevas"
admin: admin:
watch_form_message: 'Has realizado cambios que no han sido guardados. ¿Seguro que quieres abandonar la página?' watch_form_message: 'Has realizado cambios que no han sido guardados. ¿Seguro que quieres abandonar la página?'
legacy_legislation: legacy_legislation:
@@ -259,19 +260,25 @@ es:
locale: Español locale: Español
notifications: notifications:
index: index:
empty_notifications: No tienes notificaciones nuevas.
mark_all_as_read: Marcar todas como leídas
read: Todas
title: Notificaciones
unread: Nuevas
notification:
action:
comments_on: comments_on:
one: Hay un nuevo comentario en one: Hay un nuevo comentario en
other: Hay %{count} comentarios nuevos en other: Hay %{count} comentarios nuevos en
empty_notifications: No tienes notificaciones nuevas.
notifiable_hidden: Este elemento ya no está disponible.
mark_all_as_read: Marcar todas como leídas
proposal_notification: proposal_notification:
one: Hay una nueva notificación en one: Hay una nueva notificación en
other: Hay %{count} nuevas notificaciones en other: Hay %{count} nuevas notificaciones en
replies_to: replies_to:
one: Hay una respuesta nueva a tu comentario en one: Hay una respuesta nueva a tu comentario en
other: Hay %{count} nuevas respuestas a tu comentario en other: Hay %{count} nuevas respuestas a tu comentario en
title: Notificaciones mark_as_read: Marcar como leída
mark_as_unread: Marcar como no leída
notifiable_hidden: Este elemento ya no está disponible.
map: map:
title: "Distritos" title: "Distritos"
proposal_for_district: "Crea una propuesta para tu distrito" proposal_for_district: "Crea una propuesta para tu distrito"

View File

@@ -1,5 +1,8 @@
resources :notifications, only: [:index, :show] do resources :notifications, only: [:index, :show] do
put :mark_as_read, on: :member
put :mark_all_as_read, on: :collection put :mark_all_as_read, on: :collection
put :mark_as_unread, on: :member
get :read, on: :collection
end end
resources :proposal_notifications, only: [:new, :create, :show] resources :proposal_notifications, only: [:new, :create, :show]

View File

@@ -30,5 +30,6 @@ require_relative 'dev_seeds/polls'
require_relative 'dev_seeds/communities' require_relative 'dev_seeds/communities'
require_relative 'dev_seeds/legislation_processes' require_relative 'dev_seeds/legislation_processes'
require_relative 'dev_seeds/newsletters' require_relative 'dev_seeds/newsletters'
require_relative 'dev_seeds/notifications'
log "All dev seeds created successfuly 👍" log "All dev seeds created successfuly 👍"

View File

@@ -0,0 +1,16 @@
section "Creating comment notifications" do
User.all.each do |user|
debate = Debate.create!(author: user,
title: Faker::Lorem.sentence(3).truncate(60),
description: "<p>#{Faker::Lorem.paragraphs.join('</p><p>')}</p>",
tag_list: ActsAsTaggableOn::Tag.all.sample(3).join(','),
geozone: Geozone.reorder("RANDOM()").first,
terms_of_service: "1")
comment = Comment.create!(user: User.reorder("RANDOM()").first,
body: Faker::Lorem.sentence,
commentable: debate)
Notification.add(user, comment)
end
end

View File

@@ -0,0 +1,5 @@
class AddReadAtToNotifications < ActiveRecord::Migration
def change
add_column :notifications, :read_at, :timestamp
end
end

View File

@@ -640,6 +640,7 @@ ActiveRecord::Schema.define(version: 20180320104823) do
t.string "notifiable_type" t.string "notifiable_type"
t.integer "counter", default: 1 t.integer "counter", default: 1
t.datetime "emailed_at" t.datetime "emailed_at"
t.datetime "read_at"
end end
add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree

View File

@@ -705,6 +705,10 @@ FactoryBot.define do
factory :notification do factory :notification do
user user
association :notifiable, factory: :proposal association :notifiable, factory: :proposal
trait :read do
read_at Time.current
end
end end
factory :geozone do factory :geozone do

View File

@@ -4,42 +4,128 @@ feature "Notifications" do
let(:user) { create :user } let(:user) { create :user }
context "mark as read" do background do
login_as(user)
visit root_path
end
scenario "mark a single notification as read" do scenario "View all" do
notification = create :notification, user: user read1 = create(:notification, :read, user: user)
read2 = create(:notification, :read, user: user)
unread = create(:notification, user: user)
login_as user click_notifications_icon
visit notifications_path click_link "All notifications"
expect(page).to have_css ".notification", count: 1 expect(page).to have_css(".notification", count: 2)
expect(page).to have_content(read1.notifiable_title)
expect(page).to have_content(read2.notifiable_title)
expect(page).to_not have_content(unread.notifiable_title)
end
scenario "View unread" do
unread1 = create(:notification, user: user)
unread2 = create(:notification, user: user)
read = create(:notification, :read, user: user)
click_notifications_icon
click_link "Unread"
expect(page).to have_css(".notification", count: 2)
expect(page).to have_content(unread1.notifiable_title)
expect(page).to have_content(unread2.notifiable_title)
expect(page).to_not have_content(read.notifiable_title)
end
scenario "View single notification" do
proposal = create(:proposal)
notification = create(:notification, user: user, notifiable: proposal)
click_notifications_icon
first(".notification a").click first(".notification a").click
visit notifications_path expect(page).to have_current_path(proposal_path(proposal))
visit notifications_path
expect(page).to have_css ".notification", count: 0 expect(page).to have_css ".notification", count: 0
visit read_notifications_path
expect(page).to have_css ".notification", count: 1
end end
scenario "mark all notifications as read" do scenario "Mark as read", :js do
2.times { create :notification, user: user } notification1 = create(:notification, user: user)
notification2 = create(:notification, user: user)
login_as user click_notifications_icon
visit notifications_path
expect(page).to have_css ".notification", count: 2 within("#notification_#{notification1.id}") do
click_link "Mark as read"
end
expect(page).to have_css(".notification", count: 1)
expect(page).to have_content(notification2.notifiable_title)
expect(page).to_not have_content(notification1.notifiable_title)
end
scenario "Mark all as read" do
notification1 = create(:notification, user: user)
notification2 = create(:notification, user: user)
click_notifications_icon
expect(page).to have_css(".notification", count: 2)
click_link "Mark all as read" click_link "Mark all as read"
expect(page).to have_css ".notification", count: 0 expect(page).to have_css(".notification", count: 0)
expect(page).to have_current_path(notifications_path)
end end
scenario "Mark as unread", :js do
notification1 = create(:notification, :read, user: user)
notification2 = create(:notification, user: user)
click_notifications_icon
click_link "All notifications"
expect(page).to have_css(".notification", count: 1)
within("#notification_#{notification1.id}") do
click_link "Mark as unread"
end end
scenario "no notifications" do expect(page).to have_css(".notification", count: 0)
login_as user
visit notifications_path visit notifications_path
expect(page).to have_css(".notification", count: 2)
expect(page).to have_content(notification1.notifiable_title)
expect(page).to have_content(notification2.notifiable_title)
end
expect(page).to have_content "You don't have new notifications" scenario "Bell" do
create(:notification, user: user)
visit root_path
within("#notifications") do
expect(page).to have_css(".icon-circle")
end
click_notifications_icon
first(".notification a").click
within("#notifications") do
expect(page).to_not have_css(".icon-circle")
end
end
scenario "No notifications" do
click_notifications_icon
expect(page).to have_content "You don't have new notifications."
end
scenario "User not logged in" do
logout
visit root_path
expect(page).to_not have_css("#notifications")
end end
end end

View File

@@ -2,14 +2,48 @@ require 'rails_helper'
describe Notification do describe Notification do
describe "#unread (scope)" do let(:notification) { build(:notification) }
it "returns only unread notifications" do
2.times { create :notification } context "validations" do
expect(described_class.unread.size).to be 2
it "should be valid" do
expect(notification).to be_valid
end
it "should not be valid without a user" do
notification.user = nil
expect(notification).to_not be_valid
end
end
context "scopes" do
describe "#read" do
it "returns only read notifications" do
read_notification1 = create(:notification, :read)
read_notification2 = create(:notification, :read)
unread_notification = create(:notification)
expect(described_class.read).to include read_notification1
expect(described_class.read).to include read_notification2
expect(described_class.read).not_to include unread_notification
end end
end end
describe "#recent (scope)" do describe "#unread" do
it "returns only unread notifications" do
read_notification = create(:notification, :read)
unread_notification1 = create(:notification)
unread_notification2 = create(:notification)
expect(described_class.unread).to include unread_notification1
expect(described_class.unread).to include unread_notification2
expect(described_class.unread).not_to include read_notification
end
end
describe "#recent" do
it "returns notifications sorted by id descendant" do it "returns notifications sorted by id descendant" do
old_notification = create :notification old_notification = create :notification
new_notification = create :notification new_notification = create :notification
@@ -21,13 +55,39 @@ describe Notification do
end end
end end
describe "#for_render (scope)" do describe "#for_render" do
it "returns notifications including notifiable and user" do it "returns notifications including notifiable and user" do
allow(described_class).to receive(:includes).with(:notifiable).exactly(:once) allow(described_class).to receive(:includes).with(:notifiable).exactly(:once)
described_class.for_render described_class.for_render
end end
end end
end
describe "#mark_as_read" do
it "destroys notification" do
notification = create(:notification)
expect(described_class.read.size).to eq 0
expect(described_class.unread.size).to eq 1
notification.mark_as_read
expect(described_class.read.size).to eq 1
expect(described_class.unread.size).to eq 0
end
end
describe "#mark_as_unread" do
it "destroys notification" do
notification = create(:notification, :read)
expect(described_class.unread.size).to eq 0
expect(described_class.read.size).to eq 1
notification.mark_as_unread
expect(described_class.unread.size).to eq 1
expect(described_class.read.size).to eq 0
end
end
describe "#timestamp" do describe "#timestamp" do
it "returns the timestamp of the trackable object" do it "returns the timestamp of the trackable object" do
comment = create :comment comment = create :comment
@@ -37,13 +97,66 @@ describe Notification do
end end
end end
describe "#mark_as_read" do describe "#existent" do
it "destroys notification" do it "returns the notifiable when there is an existent notification of that notifiable" do
notification = create :notification user = create(:user)
expect(described_class.unread.size).to eq 1 comment = create(:comment)
notification = create(:notification, user: user, notifiable: comment)
notification.mark_as_read expect(described_class.existent(user, comment)).to eq(notification)
expect(described_class.unread.size).to eq 0 end
it "returns nil when there are no notifications of that notifiable for a user" do
user = create(:user)
comment1 = create(:comment)
comment2 = create(:comment)
create(:notification, user: user, notifiable: comment1)
expect(described_class.existent(user, comment2)).to eq(nil)
end
it "returns nil when there are notifications of a notifiable for another user" do
user1 = create(:user)
user2 = create(:user)
comment = create(:comment)
notification = create(:notification, user: user1, notifiable: comment)
expect(described_class.existent(user2, comment)).to eq(nil)
end
end
describe "#add" do
it "creates a new notification" do
user = create(:user)
comment = create(:comment)
described_class.add(user, comment)
expect(user.notifications.count).to eq(1)
end
it "increments the notification counter for an unread notification of the same notifiable" do
user = create(:user)
comment = create(:comment)
described_class.add(user, comment)
described_class.add(user, comment)
expect(user.notifications.count).to eq(1)
expect(user.notifications.first.counter).to eq(2)
end
it "creates a new notification for a read notification of the same notifiable" do
user = create(:user)
comment = create(:comment)
first_notification = described_class.add(user, comment)
first_notification.update(read_at: Time.current)
second_notification = described_class.add(user, comment)
expect(user.notifications.count).to eq(2)
expect(first_notification.counter).to eq(1)
expect(second_notification.counter).to eq(1)
end end
end end

View File

@@ -353,4 +353,7 @@ module CommonActions
fill_in "newsletter_body", with: (options[:body] || "This is a different body") fill_in "newsletter_body", with: (options[:body] || "This is a different body")
end end
def click_notifications_icon
find("#notifications a").click
end
end end