- <%= image_tag("icon_home_debate.png", size: "168x168", alt: t("welcome.debates.alt"), title: t("welcome.debates.title")) %>
+ <%= image_tag("icon_home_debate.png", size: "168x168", alt: "", title: t("welcome.debates.title")) %>
<%= t("welcome.debates.title") %>
<%= t("welcome.debates.description") %>
- <%= image_tag("icon_home_proposal.png", size: "168x168", alt: t("welcome.proposal.alt"), title: t("welcome.proposal.title")) %>
+ <%= image_tag("icon_home_proposal.png", size: "168x168", alt: "", title: t("welcome.proposal.title")) %>
<%= t("welcome.proposal.title") %>
<%= t("welcome.proposal.description") %>
- <%= image_tag("icon_home_decide.png", size: "168x168", alt: t("welcome.decide.alt"), title: t("welcome.decide.title")) %>
+ <%= image_tag("icon_home_decide.png", size: "168x168", alt: "", title: t("welcome.decide.title")) %>
<%= t("welcome.decide.title") %>
<%= t("welcome.decide.description") %>
- <%= image_tag("icon_home_do.png", size: "168x168", alt: t("welcome.do.alt"), title: t("welcome.do.title")) %>
+ <%= image_tag("icon_home_do.png", size: "168x168", alt: "", title: t("welcome.do.title")) %>
<%= t("welcome.do.title") %>
<%= t("welcome.do.description") %>
diff --git a/config/application.rb b/config/application.rb
index 395251a52..3f2a0861a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,3 +1,4 @@
+
require File.expand_path('../boot', __FILE__)
require 'rails/all'
@@ -34,5 +35,17 @@ module Consul
config.autoload_paths << Rails.root.join('lib')
config.time_zone = 'Madrid'
config.active_job.queue_adapter = :delayed_job
+
+ # Consul specific custom overrides
+ # Read more on documentation:
+ # * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md
+ # * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md
+ #
+ config.autoload_paths << "#{Rails.root}/app/controllers/custom"
+ config.autoload_paths << "#{Rails.root}/app/models/custom"
+ config.paths['app/views'].unshift(Rails.root.join('app', 'views', 'custom'))
+
end
end
+
+require "./config/application_custom.rb"
diff --git a/config/application_custom.rb b/config/application_custom.rb
new file mode 100644
index 000000000..b99944bc7
--- /dev/null
+++ b/config/application_custom.rb
@@ -0,0 +1,4 @@
+module Consul
+ class Application < Rails::Application
+ end
+end
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 250ed18f9..e24d3af0c 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -112,6 +112,7 @@ ignore_unused:
- 'admin.banners.index.filters.*'
- 'admin.debates.index.filter*'
- 'admin.proposals.index.filter*'
+ - 'admin.budgets.index.filter*'
- 'admin.spending_proposals.index.filter*'
- 'admin.organizations.index.filter*'
- 'admin.users.index.filter*'
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 7764ae0ec..e5fc916f0 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -9,8 +9,12 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
-Rails.application.config.assets.precompile += %w( ckeditor/* )
+Rails.application.config.assets.precompile += %w( ckeditor/config.js )
Rails.application.config.assets.precompile += %w( ie_lt9.js )
Rails.application.config.assets.precompile += %w( stat_graphs.js )
Rails.application.config.assets.precompile += %w( print.css )
Rails.application.config.assets.precompile += %w( ie.css )
+
+# Loads app/assets/images/custom before app/assets/images
+images_path = Rails.application.config.assets.paths
+images_path = images_path.insert(0, Rails.root.join("app", "assets", "images", "custom").to_s)
diff --git a/config/initializers/ckeditor.rb b/config/initializers/ckeditor.rb
new file mode 100644
index 000000000..58e119048
--- /dev/null
+++ b/config/initializers/ckeditor.rb
@@ -0,0 +1,4 @@
+Ckeditor.setup do |config|
+ config.assets_languages = I18n.available_locales.map(&:to_s)
+ config.assets_plugins = []
+end
diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml
index 07791e53c..5a7a591bd 100644
--- a/config/locales/activerecord.en.yml
+++ b/config/locales/activerecord.en.yml
@@ -4,6 +4,9 @@ en:
activity:
one: "activity"
other: "activities"
+ budget:
+ one: "Participatory budget"
+ other: "Participatory budgets"
comment:
one: "Comment"
other: "Comments"
diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml
index d5b7f0005..ccd0240e8 100644
--- a/config/locales/activerecord.es.yml
+++ b/config/locales/activerecord.es.yml
@@ -4,6 +4,9 @@ es:
activity:
one: "actividad"
other: "actividades"
+ budget:
+ one: "Presupuesto participativo"
+ other: "Presupuestos participativos"
comment:
one: "Comentario"
other: "Comentarios"
diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml
index 2f73f64d8..78a3a6d6c 100755
--- a/config/locales/admin.en.yml
+++ b/config/locales/admin.en.yml
@@ -32,8 +32,6 @@ en:
editing: Edit banner
form:
submit_button: Save changes
- errors:
- form:
errors:
form:
error:
@@ -60,6 +58,40 @@ en:
on_users: Users
title: Moderator activity
type: Type
+ budgets:
+ index:
+ title: Participatory budgets
+ new_link: Create new
+ filters:
+ open: Open
+ finished: Finished
+ create:
+ notice: New participatory budget created successfully!
+ new:
+ title: New participatory budget
+ create: Create budget
+ name: Budget's name
+ description: Description
+ phase: Phase
+ currency: Currency
+ show:
+ phase: Current phase
+ currency: Currency
+ groups: Groups of budget headings
+ form:
+ group: Group's name
+ no_groups: No groups created yet. Each user will be able to vote in only one heading per group.
+ add_group: Add new group
+ create_group: Create group
+ heading: Heading's name
+ add_heading: Add heading
+ amount: Amount
+ save_heading: Save heading
+ no_heading: This group has no assigned heading.
+ geozone: Scope of operation
+ table_heading: Heading
+ table_amount: Amount
+ table_geozone: Scope of operation
comments:
index:
filter: Filter
@@ -96,6 +128,7 @@ en:
activity: Moderator activity
admin: Admin menu
banner: Manage banners
+ budgets: Participatory budgets
debate_topics: Debate topics
hidden_comments: Hidden comments
hidden_debates: Hidden debates
diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml
index 5aada1ce1..496a0208d 100644
--- a/config/locales/admin.es.yml
+++ b/config/locales/admin.es.yml
@@ -58,6 +58,40 @@ es:
on_users: Usuarios
title: Actividad de los Moderadores
type: Tipo
+ budgets:
+ index:
+ title: Presupuestos participativos
+ new_link: Crear nuevo
+ filters:
+ open: Abiertos
+ finished: Terminados
+ create:
+ notice: ¡Nueva campaña de presupuestos participativos creada con éxito!
+ new:
+ title: Nuevo presupuesto ciudadano
+ create: Crear presupuesto
+ name: Nombre del presupuesto
+ description: Descripción
+ phase: Fase
+ currency: Divisa
+ show:
+ phase: Fase actual
+ currency: Divisa
+ groups: Grupos de partidas presupuestarias
+ form:
+ group: Nombre del grupo
+ no_groups: No hay grupos creados todavía. Cada usuario podrá votar en una sola partida de cada grupo.
+ add_group: Añadir nuevo grupo
+ create_group: Crear grupo
+ heading: Nombre de la partida
+ add_heading: Añadir partida
+ amount: Cantidad
+ save_heading: Guardar partida
+ no_heading: Este grupo no tiene ninguna partida asignada.
+ geozone: Ámbito de actuación
+ table_heading: Partida
+ table_amount: Cantidad
+ table_geozone: Ámbito de actuación
comments:
index:
filter: Filtro
@@ -94,6 +128,7 @@ es:
activity: Actividad de moderadores
admin: Menú de administración
banner: Gestionar banners
+ budgets: Presupuestos participativos
debate_topics: Temas de debate
hidden_comments: Comentarios ocultos
hidden_debates: Debates ocultos
diff --git a/config/locales/custom/.keep b/config/locales/custom/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 874d53ef8..d20c69885 100755
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -33,6 +33,13 @@ en:
application:
close: Close
menu: Menu
+ budget:
+ phase:
+ on_hold: On hold
+ accepting: Accepting proposals
+ selecting: Selecting
+ balloting: Balloting
+ finished: Finished
comments:
comment:
admin: Administrator
@@ -198,6 +205,7 @@ en:
more_information: More information
my_account_link: My account
my_activity_link: My activity
+ notifications: Notifications
new_notifications:
one: You have a new notification
other: You have %{count} new notifications
@@ -413,6 +421,7 @@ en:
flag: Flag as inappropriate
print:
print_button: Print this info
+ show: Show
suggest:
debate:
found:
@@ -653,19 +662,15 @@ en:
not_voting_allowed: Voting phase is closed
welcome:
debates:
- alt: Icon debates
description: For meeting, discussing and sharing the things that matter to us in our city.
title: Debates
decide:
- alt: Icon decide
description: The public decides if it accepts or rejects the most supported proposals.
title: You decide
do:
- alt: Icon it gets done
description: If the proposal is accepted by the majority, the City Council accepts it as its own and it gets done.
title: It gets done
proposal:
- alt: Icon propose
description: Open space for citizen proposals about the kind of city we want to live in.
title: You propose
verification:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 6330262a3..62b1f8059 100755
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -33,6 +33,13 @@ es:
application:
close: Cerrar
menu: Menú
+ budget:
+ phase:
+ on_hold: En pausa
+ accepting: Aceptando propuestas
+ selecting: Fase de selección
+ balloting: Fase de Votación
+ finished: Terminado
comments:
comment:
admin: Administrador
@@ -198,6 +205,7 @@ es:
more_information: Más información
my_account_link: Mi cuenta
my_activity_link: Mi actividad
+ notifications: Notificaciones
new_notifications:
one: Tienes una nueva notificación
other: Tienes %{count} notificaciones nuevas
@@ -413,6 +421,7 @@ es:
flag: Denunciar como inapropiado
print:
print_button: Imprimir esta información
+ show: Mostrar
suggest:
debate:
found:
@@ -653,19 +662,15 @@ es:
not_voting_allowed: El periodo de votación está cerrado.
welcome:
debates:
- alt: Icono debates
description: Encontrarnos, debatir y compartir lo que nos parece importante en nuestra ciudad.
title: Debates
decide:
- alt: Icono decides
description: La ciudadanía decide si acepta o rechaza las propuestas más apoyadas.
title: Decides
do:
- alt: Icono se hace
description: Si la propuesta es aceptada mayoritariamente, el Ayuntamiento la asume como propia y se hace.
title: Se hace
proposal:
- alt: Icono propones
description: Espacio abierto para propuestas ciudadanas sobre el tipo de ciudad en el que queremos vivir.
title: Propones
verification:
diff --git a/config/routes.rb b/config/routes.rb
index 5027631f3..92f3d6c22 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -156,6 +156,13 @@ Rails.application.routes.draw do
get :summary, on: :collection
end
+ resources :budgets do
+ resources :budget_groups do
+ resources :budget_headings do
+ end
+ end
+ end
+
resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do
collection { get :search}
end
diff --git a/db/migrate/20160803154011_add_emailed_at_to_notifications.rb b/db/migrate/20160803154011_add_emailed_at_to_notifications.rb
new file mode 100644
index 000000000..83e38b00f
--- /dev/null
+++ b/db/migrate/20160803154011_add_emailed_at_to_notifications.rb
@@ -0,0 +1,5 @@
+class AddEmailedAtToNotifications < ActiveRecord::Migration
+ def change
+ add_column :notifications, :emailed_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e555b2950..ff2a0f590 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160617172616) do
+ActiveRecord::Schema.define(version: 20160803154011) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -210,10 +210,10 @@ ActiveRecord::Schema.define(version: 20160617172616) do
t.string "visit_id"
t.datetime "hidden_at"
t.integer "flags_count", default: 0
- t.datetime "ignored_flag_at"
t.integer "cached_votes_total", default: 0
t.integer "cached_votes_up", default: 0
t.integer "cached_votes_down", default: 0
+ t.datetime "ignored_flag_at"
t.integer "comments_count", default: 0
t.datetime "confirmed_hide_at"
t.integer "cached_anonymous_votes_total", default: 0
@@ -337,10 +337,11 @@ ActiveRecord::Schema.define(version: 20160617172616) do
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
create_table "notifications", force: :cascade do |t|
- t.integer "user_id"
- t.integer "notifiable_id"
- t.string "notifiable_type"
- t.integer "counter", default: 1
+ t.integer "user_id"
+ t.integer "notifiable_id"
+ t.string "notifiable_type"
+ t.integer "counter", default: 1
+ t.datetime "emailed_at"
end
add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree
diff --git a/lib/email_digest.rb b/lib/email_digest.rb
index 90838f78f..209014dcc 100644
--- a/lib/email_digest.rb
+++ b/lib/email_digest.rb
@@ -1,14 +1,27 @@
class EmailDigest
- def initialize
+ attr_accessor :user, :notifications
+
+ def initialize(user)
+ @user = user
end
- def create
- User.email_digest.each do |user|
- if user.notifications.where(notifiable_type: "ProposalNotification").any?
- Mailer.proposal_notification_digest(user).deliver_later
- end
+ def notifications
+ user.notifications.not_emailed.where(notifiable_type: "ProposalNotification")
+ end
+
+ def pending_notifications?
+ notifications.any?
+ end
+
+ def deliver
+ if pending_notifications?
+ Mailer.proposal_notification_digest(user, notifications.to_a).deliver_later
end
end
+ def mark_as_emailed
+ notifications.update_all(emailed_at: Time.now)
+ end
+
end
\ No newline at end of file
diff --git a/lib/tasks/emails.rake b/lib/tasks/emails.rake
index 6670264a5..ffadebf05 100644
--- a/lib/tasks/emails.rake
+++ b/lib/tasks/emails.rake
@@ -2,8 +2,11 @@ namespace :emails do
desc "Sends email digest of proposal notifications to each user"
task digest: :environment do
- email_digest = EmailDigest.new
- email_digest.create
+ User.email_digest.find_each do |user|
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+ email_digest.mark_as_emailed
+ end
end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index e38f47c6e..db694b4ce 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -193,6 +193,10 @@ FactoryGirl.define do
currency_symbol "€"
phase 'on_hold'
+ trait :accepting do
+ phase 'accepting'
+ end
+
trait :selecting do
phase 'selecting'
end
diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb
new file mode 100644
index 000000000..123ca43e8
--- /dev/null
+++ b/spec/features/admin/budgets_spec.rb
@@ -0,0 +1,158 @@
+require 'rails_helper'
+
+feature 'Admin budgets' do
+
+ background do
+ admin = create(:administrator)
+ login_as(admin.user)
+ end
+
+ context 'Feature flag' do
+
+ xscenario 'Disabled with a feature flag' do
+ Setting['feature.budgets'] = nil
+ expect{ visit admin_budgets_path }.to raise_exception(FeatureFlags::FeatureDisabled)
+ end
+
+ end
+
+ context 'Index' do
+
+ scenario 'Displaying budgets' do
+ budget = create(:budget)
+ visit admin_budgets_path
+
+ expect(page).to have_content(budget.name)
+ expect(page).to have_content(I18n.t("budget.phase.#{budget.phase}"))
+ end
+
+ scenario 'Filters by phase' do
+ budget1 = create(:budget)
+ budget2 = create(:budget, :accepting)
+ budget3 = create(:budget, :selecting)
+ budget4 = create(:budget, :balloting)
+ budget5 = create(:budget, :finished)
+
+ visit admin_budgets_path
+ expect(page).to have_content(budget1.name)
+ expect(page).to have_content(budget2.name)
+ expect(page).to have_content(budget3.name)
+ expect(page).to have_content(budget4.name)
+ expect(page).to_not have_content(budget5.name)
+
+ click_link 'Finished'
+ expect(page).to_not have_content(budget1.name)
+ expect(page).to_not have_content(budget2.name)
+ expect(page).to_not have_content(budget3.name)
+ expect(page).to_not have_content(budget4.name)
+ expect(page).to have_content(budget5.name)
+
+ click_link 'Open'
+ expect(page).to have_content(budget1.name)
+ expect(page).to have_content(budget2.name)
+ expect(page).to have_content(budget3.name)
+ expect(page).to have_content(budget4.name)
+ expect(page).to_not have_content(budget5.name)
+ end
+
+ scenario 'Current filter is properly highlighted' do
+ filters_links = {'open' => 'Open', 'finished' => 'Finished'}
+
+ visit admin_budgets_path
+
+ expect(page).to_not have_link(filters_links.values.first)
+ filters_links.keys.drop(1).each { |filter| expect(page).to have_link(filters_links[filter]) }
+
+ filters_links.each_pair do |current_filter, link|
+ visit admin_budgets_path(filter: current_filter)
+
+ expect(page).to_not have_link(link)
+
+ (filters_links.keys - [current_filter]).each do |filter|
+ expect(page).to have_link(filters_links[filter])
+ end
+ end
+ end
+
+ end
+
+ context 'New' do
+
+ scenario 'Create budget' do
+ visit admin_budgets_path
+ click_link 'Create new'
+
+ fill_in 'budget_name', with: 'M30 - Summer campaign'
+ fill_in 'budget_description', with: 'Budgeting for summer 2017 maintenance and improvements of the road M-30'
+ select 'Accepting proposals', from: 'budget[phase]'
+
+ click_button 'Create budget'
+
+ expect(page).to have_content 'New participatory budget created successfully!'
+ expect(page).to have_content 'M30 - Summer campaign'
+ end
+
+ scenario 'Name is mandatory' do
+ visit new_admin_budget_path
+ click_button 'Create budget'
+
+ expect(page).to_not have_content 'New participatory budget created successfully!'
+ expect(page).to have_css("label.error", text: "Budget's name")
+ end
+
+ end
+
+ context 'Manage groups and headings' do
+
+ scenario 'Create group', :js do
+ create(:budget, name: 'Yearly participatory budget')
+
+ visit admin_budgets_path
+ click_link 'Yearly participatory budget'
+
+ expect(page).to have_content 'No groups created yet.'
+
+ click_link 'Add new group'
+
+ fill_in 'budget_group_name', with: 'General improvments'
+ click_button 'Create group'
+
+ expect(page).to have_content 'Yearly participatory budget'
+ expect(page).to_not have_content 'No groups created yet.'
+
+ visit admin_budgets_path
+ click_link 'Yearly participatory budget'
+
+ expect(page).to have_content 'Yearly participatory budget'
+ expect(page).to_not have_content 'No groups created yet.'
+ end
+
+ scenario 'Create heading', :js do
+ budget = create(:budget, name: 'Yearly participatory budget')
+ group = create(:budget_group, budget: budget, name: 'Districts improvments')
+
+ visit admin_budget_path(budget)
+
+ within("#budget_group_#{group.id}") do
+ expect(page).to have_content 'This group has no assigned heading.'
+ click_link 'Add heading'
+
+ fill_in 'budget_heading_name', with: 'District 9 reconstruction'
+ fill_in 'budget_heading_price', with: '6785'
+ click_button 'Save heading'
+ end
+
+ expect(page).to_not have_content 'This group has no assigned heading.'
+
+ visit admin_budget_path(budget)
+ within("#budget_group_#{group.id}") do
+ expect(page).to_not have_content 'This group has no assigned heading.'
+
+ expect(page).to have_content 'District 9 reconstruction'
+ expect(page).to have_content '6785'
+ expect(page).to have_content 'All city'
+ end
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/spec/features/emails_spec.rb b/spec/features/emails_spec.rb
index 429d74738..a9569e9b2 100644
--- a/spec/features/emails_spec.rb
+++ b/spec/features/emails_spec.rb
@@ -201,8 +201,9 @@ feature 'Emails' do
notification2 = create_proposal_notification(proposal2)
notification3 = create_proposal_notification(proposal3)
- email_digest = EmailDigest.new
- email_digest.create
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+ email_digest.mark_as_emailed
email = open_last_email
expect(email).to have_subject("Proposal notifications in Consul")
@@ -227,6 +228,11 @@ feature 'Emails' do
expect(email).to_not have_body_text(proposal3.title)
expect(email).to have_body_text(/#{account_path}/)
+
+ notification1.reload
+ notification2.reload
+ expect(notification1.emailed_at).to be
+ expect(notification2.emailed_at).to be
end
end
diff --git a/spec/features/notifications_spec.rb b/spec/features/notifications_spec.rb
index e16dedf37..5fbc51736 100644
--- a/spec/features/notifications_spec.rb
+++ b/spec/features/notifications_spec.rb
@@ -180,9 +180,10 @@ feature "Notifications" do
find(".icon-notification").click
+ notification_for_user1 = Notification.where(user: user1).first
expect(page).to have_css ".notification", count: 1
expect(page).to have_content "There is one new notification on #{proposal.title}"
- expect(page).to have_xpath "//a[@href='#{notification_path(Notification.last)}']"
+ expect(page).to have_xpath "//a[@href='#{notification_path(notification_for_user1)}']"
logout
login_as user2
@@ -190,9 +191,10 @@ feature "Notifications" do
find(".icon-notification").click
+ notification_for_user2 = Notification.where(user: user2).first
expect(page).to have_css ".notification", count: 1
expect(page).to have_content "There is one new notification on #{proposal.title}"
- expect(page).to have_xpath "//a[@href='#{notification_path(Notification.first)}']"
+ expect(page).to have_xpath "//a[@href='#{notification_path(notification_for_user2)}']"
logout
login_as user3
diff --git a/spec/features/proposal_notifications_spec.rb b/spec/features/proposal_notifications_spec.rb
index 6092289f0..a10e41a7d 100644
--- a/spec/features/proposal_notifications_spec.rb
+++ b/spec/features/proposal_notifications_spec.rb
@@ -24,6 +24,44 @@ feature 'Proposal Notifications' do
expect(page).to have_content "Please share it with others so we can make it happen!"
end
+ scenario "Send a notification (Active voter)" do
+ author = create(:user)
+ proposal = create(:proposal, author: author)
+
+ voter = create(:user, :level_two)
+ create(:vote, voter: voter, votable: proposal)
+
+ create_proposal_notification(proposal)
+
+ expect(Notification.count).to eq(1)
+ end
+
+ scenario "Send a notification (Blocked voter)" do
+ author = create(:user)
+ proposal = create(:proposal, author: author)
+
+ voter = create(:user, :level_two)
+ create(:vote, voter: voter, votable: proposal)
+ voter.block
+
+ create_proposal_notification(proposal)
+
+ expect(Notification.count).to eq(0)
+ end
+
+ scenario "Send a notification (Erased voter)" do
+ author = create(:user)
+ proposal = create(:proposal, author: author)
+
+ voter = create(:user, :level_two)
+ create(:vote, voter: voter, votable: proposal)
+ voter.erase
+
+ create_proposal_notification(proposal)
+
+ expect(Notification.count).to eq(0)
+ end
+
scenario "Show notifications" do
proposal = create(:proposal)
notification1 = create(:proposal_notification, proposal: proposal, title: "Hey guys", body: "Just wanted to let you know that...")
diff --git a/spec/helpers/geozones_helper_spec.rb b/spec/helpers/geozones_helper_spec.rb
index 605a774a6..0c0c13d70 100644
--- a/spec/helpers/geozones_helper_spec.rb
+++ b/spec/helpers/geozones_helper_spec.rb
@@ -31,4 +31,19 @@ describe GeozonesHelper do
end
end
+ describe "#geozone_name_from_id" do
+
+ it "returns geozone name if present" do
+ g1 = create(:geozone, name: "AAA")
+ g2 = create(:geozone, name: "BBB")
+
+ expect(geozone_name_from_id(g1.id)).to eq "AAA"
+ expect(geozone_name_from_id(g2.id)).to eq "BBB"
+ end
+
+ it "returns default string for no geozone if geozone is blank" do
+ expect(geozone_name_from_id(nil)).to eq "All city"
+ end
+ end
+
end
diff --git a/spec/lib/email_digests_spec.rb b/spec/lib/email_digests_spec.rb
index 6fc6eef53..ae2122793 100644
--- a/spec/lib/email_digests_spec.rb
+++ b/spec/lib/email_digests_spec.rb
@@ -2,8 +2,126 @@ require 'rails_helper'
describe EmailDigest do
- describe "create" do
- pending "only send unread notifications"
+ describe "notifications" do
+
+ it "returns notifications for a user" do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification1 = create(:notification, notifiable: proposal_notification, user: user1)
+ notification2 = create(:notification, notifiable: proposal_notification, user: user2)
+
+ email_digest = EmailDigest.new(user1)
+
+ expect(email_digest.notifications).to include(notification1)
+ expect(email_digest.notifications).to_not include(notification2)
+ end
+
+ it "returns only proposal notifications" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ comment = create(:comment)
+
+ notification1 = create(:notification, notifiable: proposal_notification, user: user)
+ notification2 = create(:notification, notifiable: comment, user: user)
+
+ email_digest = EmailDigest.new(user)
+
+ expect(email_digest.notifications).to include(notification1)
+ expect(email_digest.notifications).to_not include(notification2)
+ end
+
+ end
+
+ describe "pending_notifications?" do
+
+ it "returns true when notifications have not been emailed" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user)
+
+ email_digest = EmailDigest.new(user)
+ expect(email_digest.pending_notifications?).to be
+ end
+
+ it "returns false when notifications have been emailed" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user, emailed_at: Time.now)
+
+ email_digest = EmailDigest.new(user)
+ expect(email_digest.pending_notifications?).to_not be
+ end
+
+ it "returns false when there are no notifications for a user" do
+ user = create(:user)
+ email_digest = EmailDigest.new(user)
+ expect(email_digest.pending_notifications?).to_not be
+ end
+
+ end
+
+ describe "deliver" do
+
+ it "delivers email if notifications pending" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user)
+
+ reset_mailer
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+
+ email = open_last_email
+ expect(email).to have_subject("Proposal notifications in Consul")
+ end
+
+ it "does not deliver email if no notifications pending" do
+ user = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification = create(:notification, notifiable: proposal_notification, user: user, emailed_at: Time.now)
+
+ reset_mailer
+ email_digest = EmailDigest.new(user)
+ email_digest.deliver
+
+ expect(all_emails.count).to eq(0)
+ end
+
+ end
+
+ describe "mark_as_emailed" do
+
+ it "marks notifications as emailed" do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ proposal_notification = create(:proposal_notification)
+ notification1 = create(:notification, notifiable: proposal_notification, user: user1)
+ notification2 = create(:notification, notifiable: proposal_notification, user: user1)
+ notification3 = create(:notification, notifiable: proposal_notification, user: user2)
+
+ expect(notification1.emailed_at).to_not be
+ expect(notification2.emailed_at).to_not be
+ expect(notification3.emailed_at).to_not be
+
+ email_digest = EmailDigest.new(user1)
+ email_digest.mark_as_emailed
+
+ notification1.reload
+ notification2.reload
+ notification3.reload
+ expect(notification1.emailed_at).to be
+ expect(notification2.emailed_at).to be
+ expect(notification3.emailed_at).to_not be
+ end
+
end
end
\ No newline at end of file
diff --git a/spec/models/custom/.keep b/spec/models/custom/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/models/custom/residence_spec.rb b/spec/models/custom/residence_spec.rb
new file mode 100644
index 000000000..81c0b2e9b
--- /dev/null
+++ b/spec/models/custom/residence_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe Verification::Residence do
+
+ let(:residence) { build(:verification_residence, document_number: "12345678Z") }
+
+ describe "verification" do
+
+ describe "postal code" do
+ it "should be valid with postal codes starting with 280" do
+ residence.postal_code = "28012"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(0)
+
+ residence.postal_code = "28023"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(0)
+ end
+
+ it "should not be valid with postal codes not starting with 280" do
+ residence.postal_code = "12345"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(1)
+
+ residence.postal_code = "13280"
+ residence.valid?
+ expect(residence.errors[:postal_code].size).to eq(1)
+ expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered.")
+ end
+ end
+
+ end
+
+end
diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb
index 4958204ec..247cd9e66 100644
--- a/spec/models/proposal_spec.rb
+++ b/spec/models/proposal_spec.rb
@@ -367,6 +367,50 @@ describe Proposal do
end
end
+ describe "voters" do
+
+ it "returns users that have voted for the proposal" do
+ proposal = create(:proposal)
+ voter1 = create(:user, :level_two)
+ voter2 = create(:user, :level_two)
+ voter3 = create(:user, :level_two)
+
+ create(:vote, voter: voter1, votable: proposal)
+ create(:vote, voter: voter2, votable: proposal)
+
+ expect(proposal.voters).to include(voter1)
+ expect(proposal.voters).to include(voter2)
+ expect(proposal.voters).to_not include(voter3)
+ end
+
+ it "does not return users that have been erased" do
+ proposal = create(:proposal)
+ voter1 = create(:user, :level_two)
+ voter2 = create(:user, :level_two)
+
+ create(:vote, voter: voter1, votable: proposal)
+ create(:vote, voter: voter2, votable: proposal)
+ voter2.erase
+
+ expect(proposal.voters).to include(voter1)
+ expect(proposal.voters).to_not include(voter2)
+ end
+
+ it "does not return users that have been blocked" do
+ proposal = create(:proposal)
+ voter1 = create(:user, :level_two)
+ voter2 = create(:user, :level_two)
+
+ create(:vote, voter: voter1, votable: proposal)
+ create(:vote, voter: voter2, votable: proposal)
+ voter2.block
+
+ expect(proposal.voters).to include(voter1)
+ expect(proposal.voters).to_not include(voter2)
+ end
+
+ end
+
describe "search" do
context "attributes" do
diff --git a/spec/models/residence_spec.rb b/spec/models/residence_spec.rb
index 5288a20b5..968a80e62 100644
--- a/spec/models/residence_spec.rb
+++ b/spec/models/residence_spec.rb
@@ -30,29 +30,6 @@ describe Verification::Residence do
expect(residence.errors[:date_of_birth]).to include("You must be at least 16 years old")
end
- describe "postal code" do
- it "should be valid with postal codes starting with 280" do
- residence.postal_code = "28012"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(0)
-
- residence.postal_code = "28023"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(0)
- end
-
- it "should not be valid with postal codes not starting with 280" do
- residence.postal_code = "12345"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(1)
-
- residence.postal_code = "13280"
- residence.valid?
- expect(residence.errors[:postal_code].size).to eq(1)
- expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered.")
- end
- end
-
it "should validate uniquness of document_number" do
user = create(:user)
residence.user = user
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d54f3a42e..b6084a971 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -325,6 +325,34 @@ describe User do
end
+ describe "scopes" do
+
+ describe "active" do
+
+ it "returns users that have not been erased" do
+ user1 = create(:user, erased_at: nil)
+ user2 = create(:user, erased_at: nil)
+ user3 = create(:user, erased_at: Time.now)
+
+ expect(User.active).to include(user1)
+ expect(User.active).to include(user2)
+ expect(User.active).to_not include(user3)
+ end
+
+ it "returns users that have not been blocked" do
+ user1 = create(:user)
+ user2 = create(:user)
+ user3 = create(:user)
+ user3.block
+
+ expect(User.active).to include(user1)
+ expect(User.active).to include(user2)
+ expect(User.active).to_not include(user3)
+ end
+
+ end
+ end
+
describe "self.search" do
it "find users by email" do
user1 = create(:user, email: "larry@consul.dev")