diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 11397297..89d18c3f 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -181,6 +181,7 @@ linters: - "app/views/tracks/index.html.haml" - "app/views/tracks/show.html.haml" - "app/views/conferences/_call_for_tracks.html.haml" + - "app/views/admin/tracks/_change_state_dropdown.html.haml" # Offense count: 223 InstanceVariables: @@ -245,6 +246,7 @@ linters: - "app/views/tracks/_form.html.haml" - "app/views/admin/cfps/_tracks_cfp.html.haml" - "app/views/conferences/_call_for_tracks.html.haml" + - "app/views/admin/tracks/_change_state_dropdown.html.haml" # Offense count: 32 IdNames: diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index 2cb6ec2b..3d51a677 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -20,6 +20,7 @@ module Admin def create @track = @program.tracks.new(track_params) @track.state = 'confirmed' + @track.cfp_active = true if @track.save redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title), notice: 'Track successfully created.' @@ -60,10 +61,55 @@ module Admin end end + def restart + update_state(:restart, "Review for #{@track.name} started!") + end + + def to_accept + update_state(:to_accept, "Track #{@track.name} marked as a possible acceptance!") + end + + def accept + if @track.room && @track.start_date && @track.end_date + update_state(:accept, "Track #{@track.name} accepted!") + else + flash[:alert] = 'Please make sure that the track has a room and start/end dates before accepting it' + redirect_to edit_admin_conference_program_track_path(@conference.short_title, @track) + end + end + + def confirm + update_state(:confirm, "Track #{@track.name} confirmed!") + end + + def to_reject + update_state(:to_reject, "Track #{@track.name} marked as a possible rejection!") + end + + def reject + update_state(:reject, "Track #{@track.name} rejected!") + end + + def cancel + update_state(:cancel, "Track #{@track.name} canceled!") + end + private def track_params params.require(:track).permit(:name, :description, :color, :short_name, :cfp_active, :start_date, :end_date, :room_id) end + + def update_state(transition, notice) + errors = @track.update_state(transition) + + if errors.blank? + flash[:notice] = notice + else + flash[:error] = errors + end + + redirect_back_or_to(admin_conference_program_tracks_path(conference_id: @conference.short_title)) + end end end diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index 59deae3c..9305950e 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -4,7 +4,7 @@ class TracksController < ApplicationController load_and_authorize_resource through: :program, find_by: :short_name def index - @tracks = current_user.tracks.where(program: @program) + @tracks = @tracks.where(submitter: current_user) end def show; end @@ -38,9 +38,33 @@ class TracksController < ApplicationController end end + def restart + update_state(:restart, "Track #{@track.name} re-submitted.") + end + + def confirm + update_state(:confirm, "Track #{@track.name} confirmed.") + end + + def withdraw + update_state(:withdraw, "Track #{@track.name} withdrawn.") + end + private def track_params - params.require(:track).permit(:name, :description, :color, :short_name) + params.require(:track).permit(:name, :description, :color, :short_name, :start_date, :end_date) + end + + def update_state(transition, notice) + errors = @track.update_state(transition) + + if errors.blank? + flash[:notice] = notice + else + flash[:error] = errors + end + + redirect_back_or_to(conference_program_tracks_path(conference_id: @conference.short_title)) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fdef7b4a..040927c1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -56,7 +56,7 @@ module ApplicationHelper end def tracks(conference) - all = conference.program.tracks.map { |t| t.name if !t.self_organized? || t.confirmed? && t.cfp_active }.compact + all = conference.program.tracks.where(state: 'confirmed', cfp_active: true).pluck(:name) first = all[0...-1] last = all[-1] ts = '' diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 012bd930..dbc609ac 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -39,4 +39,12 @@ module EventsHelper content_tag :span, 'REPLACEMENT', class: (['label', 'label-info'] + label_classes) end end + + def track_selector_input(form) + if @program.tracks.any? + form.input :track_id, as: :select, + collection: @program.tracks.where(state: 'confirmed', cfp_active: true).pluck(:name, :id), + include_blank: true + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index e5dbd7e4..63ec68dc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -100,8 +100,10 @@ class Ability track.new_record? && track.program.cfps.for_tracks.try(:open?) end - can [:index, :show, :edit, :update], Track do |track| - user == track.submitter + can [:index, :show, :restart, :confirm, :withdraw], Track, submitter_id: user.id + + can [:edit, :update], Track do |track| + user == track.submitter && !(track.accepted? || track.confirmed?) end end diff --git a/app/models/admin_ability.rb b/app/models/admin_ability.rb index 716aecb0..b0c9d4c9 100644 --- a/app/models/admin_ability.rb +++ b/app/models/admin_ability.rb @@ -275,6 +275,10 @@ class AdminAbility can :manage, Track, id: track_ids_for_track_organizer + cannot [:edit, :update], Track do |track| + track.self_organized_and_accepted_or_confirmed? + end + # Show Roles in the admin sidebar and allow authorization of the index action can [:index, :show], Role do |role| role.resource_type == 'Conference' || role.resource_type == 'Track' diff --git a/app/models/event.rb b/app/models/event.rb index 8436aac9..682eb6c8 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -45,6 +45,7 @@ class Event < ActiveRecord::Base validates :max_attendees, numericality: { only_integer: true, greater_than_or_equal_to: 1, allow_nil: true } validate :max_attendees_no_more_than_room_size + validate :acceptable_track scope :confirmed, -> { where(state: 'confirmed') } scope :canceled, -> { where(state: 'canceled') } @@ -303,4 +304,11 @@ class Event < ActiveRecord::Base def conference_id program.conference_id end + + ## + # Allow only confirmed tracks that belong to the same program and are included in the cfp + def acceptable_track + return unless track && track.program && program + errors.add(:track, 'is invalid') unless track.confirmed? && track.cfp_active && track.program == program + end end diff --git a/app/models/track.rb b/app/models/track.rb index a53471bc..3743cd79 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -22,13 +22,13 @@ class Track < ActiveRecord::Base } validates :state, presence: true, - inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) }, - if: :self_organized? - validates :cfp_active, inclusion: { in: [true, false] }, if: :self_organized? + inclusion: { in: %w(new to_accept accepted confirmed to_reject rejected canceled withdrawn) } + validates :cfp_active, inclusion: { in: [true, false] } validates :start_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :end_date, presence: true, if: :self_organized_and_accepted_or_confirmed? validates :room, presence: true, if: :self_organized_and_accepted_or_confirmed? - validate :valid_dates, if: :self_organized_and_accepted_or_confirmed? + validate :valid_dates + validate :valid_room, if: :self_organized_and_accepted_or_confirmed? before_validation :capitalize_color @@ -45,7 +45,7 @@ class Track < ActiveRecord::Base event :restart do transitions to: :new, from: [:rejected, :withdrawn, :canceled] end - event :readiness_to_accept do + event :to_accept do transitions to: :to_accept, from: [:new] end event :accept do @@ -54,7 +54,7 @@ class Track < ActiveRecord::Base event :confirm do transitions to: :confirmed, from: [:accepted], on_transition: :assign_role_to_submitter end - event :readiness_to_reject do + event :to_reject do transitions to: :to_reject, from: [:new] end event :reject do @@ -107,6 +107,7 @@ class Track < ActiveRecord::Base events.each do |event| event.track = nil + event.save! end end @@ -137,6 +138,19 @@ class Track < ActiveRecord::Base self_organized? && (accepted? || confirmed?) end + def update_state(transition) + error = '' + + begin + send(transition) + rescue Transitions::InvalidTransition => e + error += "State update failed. #{e.message} " + end + + error += errors.full_messages.join(', ') unless save + error + end + private def generate_guid @@ -162,26 +176,32 @@ class Track < ActiveRecord::Base end def valid_dates - return unless start_date && end_date - - if program && program.conference && program.conference.start_date && (start_date < program.conference.start_date) - errors.add(:start_date, "can't be before the conference start date (#{program.conference.end_date})") + if start_date && program && program.conference && program.conference.start_date && (start_date < program.conference.start_date) + errors.add(:start_date, "can't be before the conference start date (#{program.conference.start_date})") end - if program && program.conference && program.conference.start_date && (end_date < program.conference.start_date) - errors.add(:end_date, "can't be before the conference start date (#{program.conference.end_date})") + if end_date && program && program.conference && program.conference.start_date && (end_date < program.conference.start_date) + errors.add(:end_date, "can't be before the conference start date (#{program.conference.start_date})") end - if program && program.conference && program.conference.end_date && (start_date > program.conference.end_date) + if start_date && program && program.conference && program.conference.end_date && (start_date > program.conference.end_date) errors.add(:start_date, "can't be after the conference end date (#{program.conference.end_date})") end - if program && program.conference && program.conference.end_date && (end_date > program.conference.end_date) + if end_date && program && program.conference && program.conference.end_date && (end_date > program.conference.end_date) errors.add(:end_date, "can't be after the conference end date (#{program.conference.end_date})") end - if start_date > end_date - errors.add(:start_date, 'can\'t be after the end_date') + if start_date && end_date && (start_date > end_date) + errors.add(:start_date, 'can\'t be after the end date') + end + end + + ## + # Verify that the room is a room of the conference + def valid_room + if room && room.venue && room.venue.conference && program && program.conference && (program.conference != room.venue.conference) + errors.add(:room, "must be a room of #{program.conference.venue.name}") end end end diff --git a/app/views/admin/tracks/_change_state_dropdown.html.haml b/app/views/admin/tracks/_change_state_dropdown.html.haml new file mode 100644 index 00000000..0bbbcd7f --- /dev/null +++ b/app/views/admin/tracks/_change_state_dropdown.html.haml @@ -0,0 +1,34 @@ +- if track.transition_possible? :restart + %li= link_to 'Start review', + restart_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "restart_track_#{track.id}" + +- if track.transition_possible? :to_accept + %li= link_to 'Mark as possible acceptance', + to_accept_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "to_accept_track_#{track.id}" + +- if track.transition_possible? :accept + %li= link_to 'Accept track request', + accept_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "accept_track_#{track.id}" + +- if track.transition_possible? :confirm + %li= link_to 'Confirm track', + confirm_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "confirm_track_#{track.id}" + +- if track.transition_possible? :to_reject + %li= link_to 'Mark as possible rejection', + to_reject_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "to_reject_track_#{track.id}" + +- if track.transition_possible? :reject + %li= link_to 'Reject track request', + reject_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, confirm: 'Are you sure?', id: "reject_track_#{track.id}" + +- if track.transition_possible? :cancel + %li= link_to 'Cancel track request', + cancel_admin_conference_program_track_path(@conference.short_title, track), + method: :patch, id: "cancel_track_#{track.id}" diff --git a/app/views/admin/tracks/_form.html.haml b/app/views/admin/tracks/_form.html.haml index cb2e8b01..eb12561b 100644 --- a/app/views/admin/tracks/_form.html.haml +++ b/app/views/admin/tracks/_form.html.haml @@ -19,6 +19,5 @@ - else Please add a venue with rooms, if you want to select a room for the track. = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, hint: markdown_hint - - if @track.self_organized? - = f.input :cfp_active, label: 'Allow event submitters to select this track for their proposal' + = f.input :cfp_active, label: 'Allow event submitters to select this track for their proposal' = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index 4da97a18..0cfe0ff2 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -40,21 +40,24 @@ = track.color %td - if track.self_organized? - = track.state + .btn-group + %button{ type: 'button', class: 'btn btn-link dropdown-toggle', 'data-toggle' => 'dropdown' } + = track.state.humanize + %span.caret + %ul.dropdown-menu{ role: 'menu' } + = render 'change_state_dropdown', track: track - else = track.state.humanize %td - - if track.self_organized? - = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, - class: 'switch-checkbox', method: :patch, - url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", - data: { size: 'small', - on_color: 'success', - off_color: 'warning', - on_text: 'Yes', - off_text: 'No' } - - else - %i.fa.fa-check + = check_box_tag "#{@conference.short_title}_#{track.short_name}", track.id, track.cfp_active, + class: 'switch-checkbox', method: :patch, + url: toggle_cfp_inclusion_admin_conference_program_track_path(@conference.short_title, id: track.short_name)+"?included=", + data: { size: 'small', + on_color: 'success', + off_color: 'warning', + on_text: 'Yes', + off_text: 'No' } + %td - if track.room = link_to track.room.name, admin_conference_venue_room_path(@conference.short_title, track.room.id) @@ -72,8 +75,9 @@ N/A %td .btn-group{role: "group"} - = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), - method: :get, class: 'btn btn-primary' + - if can? :edit, track + = link_to 'Edit', edit_admin_conference_program_track_path(@conference.short_title, track), + method: :get, class: 'btn btn-primary' - if can? :destroy, track = link_to 'Delete', admin_conference_program_track_path(@conference.short_title, track), method: :delete, class: 'btn btn-danger', diff --git a/app/views/proposals/_proposal_form.html.haml b/app/views/proposals/_proposal_form.html.haml index f31651a1..4b66240c 100644 --- a/app/views/proposals/_proposal_form.html.haml +++ b/app/views/proposals/_proposal_form.html.haml @@ -6,10 +6,7 @@ = speaker_selector_input f - - if @program.tracks.any? - = f.input :track_id, as: :select, - collection: @program.tracks.map {|track| ["#{track.name}", track.id] }, - include_blank: true + = track_selector_input f = f.input :event_type_id, as: :select, collection: @conference.program.event_types.map {|type| ["#{type.title} - #{show_time(type.length)}", type.id, diff --git a/app/views/tracks/_form.html.haml b/app/views/tracks/_form.html.haml index 0e48a851..2a55df5a 100644 --- a/app/views/tracks/_form.html.haml +++ b/app/views/tracks/_form.html.haml @@ -9,9 +9,11 @@ Track .row .col-md-12 - = semantic_form_for(@track, url: (@track.new_record? ? conference_program_tracks_path : conference_program_track_path(@conference.short_title, @track))) do |f| + = semantic_form_for(@track, url: (@track.new_record? ? conference_program_tracks_path(@conference.short_title) : conference_program_track_path(@conference.short_title, @track))) do |f| = f.input :name = f.input :short_name, hint: "A short and unique handle for the track, using only letters, numbers, underscores, and dashes. This will be used to identify the track in URLs etc. Example: 'my_awesome_track'", input_html: { required: 'required', pattern: '[a-zA-Z0-9_-]+', title: 'Only letters, numbers, underscores, and dashes.' } = f.input :color, input_html: {size: 6, type: 'color'}, required: true + = f.input :start_date, as: :string, input_html: { id: 'registration-period-start-datepicker', start_date: @conference.start_date, end_date: @conference.end_date, readonly: 'readonly' } + = f.input :end_date, as: :string, input_html: { id: 'registration-period-end-datepicker', readonly: 'readonly' } = f.input :description, input_html: {rows: 2, data: { provide: 'markdown-editable' } }, required: true, hint: markdown_hint = f.action :submit, as: :button, button_html: { class: 'btn btn-primary' } diff --git a/app/views/tracks/index.html.haml b/app/views/tracks/index.html.haml index 7e16304f..fd1235d4 100644 --- a/app/views/tracks/index.html.haml +++ b/app/views/tracks/index.html.haml @@ -16,6 +16,8 @@ %th Description %th Color %th State + %th Start Date + %th End Date %th Actions %tbody - @tracks.each do |track| @@ -34,10 +36,31 @@ %td = track.state.humanize %td - = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), - method: :get, class: 'btn btn-primary' + - if track.start_date + = track.start_date.strftime('%A, %B %-d. %Y') + - else + N/A + %td + - if track.end_date + = track.end_date.strftime('%A, %B %-d. %Y') + - else + N/A + %td + - if track.transition_possible? :confirm + = link_to 'Confirm', confirm_conference_program_track_path(@conference.short_title, track), + method: :patch, class: 'btn btn-mini btn-success', id: "confirm_track_#{track.id}" + - if track.transition_possible? :withdraw + = link_to 'Withdraw', withdraw_conference_program_track_path(@conference.short_title, track), + method: :patch, data: { confirm: 'Are you sure you want to withdraw this track request?' }, + class: 'btn btn-mini btn-warning', id: "withdraw_track_request_#{track.id}" + - if track.transition_possible? :restart + = link_to 'Re-Submit', restart_conference_program_track_path(@conference.short_title, track), + method: :patch, class: 'btn btn-mini btn-success', id: "resubmit_track_request_#{track.id}" + - if can? :edit, track + = link_to 'Edit', edit_conference_program_track_path(@conference.short_title, track), + method: :get, class: 'btn btn-primary' .row .col-md-12 - - if can? :create, @track + - if can? :new, @program.tracks.new = link_to "New Track request", new_conference_program_track_path(@conference.short_title), class: 'btn btn-success pull-right' diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index 37c180ae..c5bc27e5 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -17,10 +17,25 @@ State: %dd = @track.state.humanize + %dt + Start date: + %dd + - if @track.start_date + = @track.start_date.strftime('%A, %B %-d. %Y') + - else + N/A + %dt + End date: + %dd + - if @track.end_date + = @track.end_date.strftime('%A, %B %-d. %Y') + - else + N/A %dt Description %dd = @track.description .row .col-md-12.text-right - = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' + - if can? :edit, @track + = link_to 'Edit Track request', edit_conference_program_track_path(@conference.short_title, @track), class: 'btn btn-primary' diff --git a/config/routes.rb b/config/routes.rb index be507d35..d4d6d0ee 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -69,6 +69,13 @@ Osem::Application.routes.draw do resources :tracks do member do patch :toggle_cfp_inclusion + patch :restart + patch :to_accept + patch :accept + patch :confirm + patch :to_reject + patch :reject + patch :cancel end end resources :event_types @@ -149,7 +156,13 @@ Osem::Application.routes.draw do patch '/restart' => 'proposals#restart' end end - resources :tracks, except: :destroy + resources :tracks, except: :destroy do + member do + patch :restart + patch :confirm + patch :withdraw + end + end end # TODO: change conference_registrations to singular resource diff --git a/db/migrate/20170720134353_make_track_cfp_active_not_null.rb b/db/migrate/20170720134353_make_track_cfp_active_not_null.rb new file mode 100644 index 00000000..0f75d046 --- /dev/null +++ b/db/migrate/20170720134353_make_track_cfp_active_not_null.rb @@ -0,0 +1,14 @@ +class MakeTrackCfpActiveNotNull < ActiveRecord::Migration + class TmpTrack < ActiveRecord::Base + self.table_name = 'tracks' + end + + def change + TmpTrack.where(cfp_active: nil).each do |track| + track.cfp_active = true + track.save! + end + + change_column_null :tracks, :cfp_active, false + end +end diff --git a/db/schema.rb b/db/schema.rb index aa2f160d..f8cc3ddc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -520,7 +520,7 @@ ActiveRecord::Schema.define(version: 20170807092805) do t.integer "program_id" t.string "short_name", null: false t.string "state", default: "new", null: false - t.boolean "cfp_active" + t.boolean "cfp_active", null: false t.integer "submitter_id" t.integer "room_id" t.date "start_date" diff --git a/spec/controllers/admin/tracks_controller_spec.rb b/spec/controllers/admin/tracks_controller_spec.rb index ed76fe68..70ca48a1 100644 --- a/spec/controllers/admin/tracks_controller_spec.rb +++ b/spec/controllers/admin/tracks_controller_spec.rb @@ -5,7 +5,7 @@ describe Admin::TracksController do let(:conference) { create(:conference) } let!(:track) { create(:track, program: conference.program, color: '#800080') } - let!(:self_organized_track) { create(:track, :self_organized, program: conference.program) } + let!(:self_organized_track) { create(:track, :self_organized, program: conference.program, name: 'My awesome track') } before :each do sign_in(admin) @@ -83,6 +83,7 @@ describe Admin::TracksController do it 'the new tracks has the correct attributes' do expect(assigns(:track).state).to eq 'confirmed' + expect(assigns(:track).cfp_active).to eq true end end @@ -257,4 +258,204 @@ describe Admin::TracksController do end end end + + describe 'PATCH #restart' do + before :each do + self_organized_track.state = 'canceled' + self_organized_track.save! + patch :restart, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Review for My awesome track started!' + end + + it 'changes the track\'s state to new' do + expect(self_organized_track.state).to eq 'new' + end + end + + describe 'PATCH #to_accept' do + before :each do + patch :to_accept, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track marked as a possible acceptance!' + end + + it 'changes the track\'s state to to_accept' do + expect(self_organized_track.state).to eq 'to_accept' + end + end + + describe 'PATCH #accept' do + shared_examples 'fails to accept' do |start_date, end_date, room| + before :each do + self_organized_track.start_date = start_date ? Date.today : nil + self_organized_track.end_date = end_date ? Date.today : nil + if room + conference.venue = create(:venue) + self_organized_track.room = create(:room, venue: conference.venue) + else + self_organized_track.room = nil + end + self_organized_track.save! + + patch :accept, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'redirects to Tracks#edit' do + expect(response).to redirect_to edit_admin_conference_program_track_path(conference.short_title, self_organized_track) + end + + it 'shows message in flash alert' do + expect(flash[:alert]).to eq 'Please make sure that the track has a room and start/end dates before accepting it' + end + end + + context 'has start_date, end_date and room' do + before :each do + self_organized_track.start_date = Date.today + self_organized_track.end_date = Date.today + conference.venue = create(:venue) + self_organized_track.room = create(:room, venue: conference.venue) + self_organized_track.save! + + patch :accept, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track accepted!' + end + + it 'changes the track\'s state to accepted' do + expect(self_organized_track.state).to eq 'accepted' + end + end + + context 'has start_date and end_date' do + it_behaves_like 'fails to accept', true, true, false + end + + context 'has start_date and room' do + it_behaves_like 'fails to accept', true, false, true + end + + context 'has start_date' do + it_behaves_like 'fails to accept', true, false, false + end + + context 'has end_date and room' do + it_behaves_like 'fails to accept', false, true, true + end + + context 'has end_date' do + it_behaves_like 'fails to accept', false, true, false + end + + context 'has room' do + it_behaves_like 'fails to accept', false, false, true + end + + context 'has non of start_date, end_date, room' do + it_behaves_like 'fails to accept', false, false, false + end + end + + describe 'PATCH #confirm' do + before :each do + self_organized_track.state = 'accepted' + self_organized_track.save! + patch :confirm, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track confirmed!' + end + + it 'changes the track\'s state to confirmed' do + expect(self_organized_track.state).to eq 'confirmed' + end + end + + describe 'PATCH #to_reject' do + before :each do + patch :to_reject, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track marked as a possible rejection!' + end + + it 'changes the track\'s state to to_reject' do + expect(self_organized_track.state).to eq 'to_reject' + end + end + + describe 'PATCH #reject' do + before :each do + patch :reject, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track rejected!' + end + + it 'changes the track\'s state to rejected' do + expect(self_organized_track.state).to eq 'rejected' + end + end + + describe 'PATCH #cancel' do + before :each do + self_organized_track.state = 'confirmed' + self_organized_track.save! + patch :cancel, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track canceled!' + end + + it 'changes the track\'s state to canceled' do + expect(self_organized_track.state).to eq 'canceled' + end + end end diff --git a/spec/controllers/tracks_controller_spec.rb b/spec/controllers/tracks_controller_spec.rb index 373435c2..0e3a0553 100644 --- a/spec/controllers/tracks_controller_spec.rb +++ b/spec/controllers/tracks_controller_spec.rb @@ -1,12 +1,11 @@ require 'spec_helper' describe TracksController do - # A regular user should be used when the track requests have been enabled let(:user) { create(:admin) } let(:conference) { create(:conference) } let!(:regular_track) { create(:track, program: conference.program) } - let!(:self_organized_track) { create(:track, :self_organized, program: conference.program, submitter: user, color: '#800080') } + let!(:self_organized_track) { create(:track, :self_organized, program: conference.program, submitter: user, name: 'My awesome track', color: '#800080') } before :each do sign_in(user) @@ -176,4 +175,67 @@ describe TracksController do end end end + + describe 'PATCH #restart' do + before :each do + self_organized_track.state = 'withdrawn' + self_organized_track.save! + patch :restart, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track re-submitted.' + end + + it 'changes the track\'s state to new' do + expect(self_organized_track.state).to eq 'new' + end + end + + describe 'PATCH #confirm' do + before :each do + self_organized_track.state = 'accepted' + self_organized_track.save! + patch :confirm, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track confirmed.' + end + + it 'changes the track\'s state to confirmed' do + expect(self_organized_track.state).to eq 'confirmed' + end + end + + describe 'PATCH #withdraw' do + before :each do + self_organized_track.state = 'confirmed' + self_organized_track.save! + patch :withdraw, conference_id: conference.short_title, id: self_organized_track.short_name + self_organized_track.reload + end + + it 'assigns the correct track' do + expect(assigns(:track)).to eq self_organized_track + end + + it 'shows message in flash notice' do + expect(flash[:notice]).to eq 'Track My awesome track withdrawn.' + end + + it 'changes the track\'s state to withdrawn' do + expect(self_organized_track.state).to eq 'withdrawn' + end + end end diff --git a/spec/factories/tracks.rb b/spec/factories/tracks.rb index d30a686e..d61c9a1b 100644 --- a/spec/factories/tracks.rb +++ b/spec/factories/tracks.rb @@ -5,12 +5,16 @@ FactoryGirl.define do color { Faker::Color.hex_color } short_name { SecureRandom.urlsafe_base64(5) } state 'confirmed' + cfp_active true program trait :self_organized do association :submitter, factory: :user state 'new' cfp_active false + start_date { Date.today } + end_date { Date.today } + room end end end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 2144cec6..ff3f14cb 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -26,6 +26,7 @@ describe 'User' do let(:program_with_cfp) { create(:program, :with_cfp) } let(:program_without_cfp) { create(:program) } + let(:program_with_call_for_tracks) { create(:cfp, cfp_type: 'tracks').program } let(:conference_with_open_registration) { create(:conference) } let!(:open_registration_period) { create(:registration_period, conference: conference_with_open_registration, start_date: Date.current - 6.days) } let(:conference_with_closed_registration) { create(:conference) } @@ -82,6 +83,10 @@ describe 'User' do let(:user_event_with_cfp) { create(:event, users: [user], program: program_with_cfp) } let(:user_commercial) { create(:commercial, commercialable: user_event_with_cfp) } + let(:user_self_organized_track) { create(:track, :self_organized, submitter: user) } + let(:accepted_user_self_organized_track) { create(:track, :self_organized, submitter: user, state: 'accepted') } + let(:confirmed_user_self_organized_track) { create(:track, :self_organized, submitter: user, state: 'confirmed') } + let(:other_self_organized_track) { create(:track, :self_organized) } it{ should be_able_to(:manage, user) } @@ -114,6 +119,31 @@ describe 'User' do it{ should be_able_to(:create, user_event_with_cfp.commercials.new) } it{ should be_able_to(:manage, user_commercial) } it{ should_not be_able_to(:manage, commercial_event_unconfirmed) } + + it{ should be_able_to(:new, Track.new(program: program_with_call_for_tracks)) } + it{ should be_able_to(:create, Track.new(program: program_with_call_for_tracks)) } + it{ should_not be_able_to(:new, Track.new(program: program_without_cfp)) } + it{ should_not be_able_to(:create, Track.new(program: program_without_cfp)) } + + it{ should be_able_to(:index, user_self_organized_track) } + it{ should be_able_to(:show, user_self_organized_track) } + it{ should be_able_to(:restart, user_self_organized_track) } + it{ should be_able_to(:confirm, user_self_organized_track) } + it{ should be_able_to(:withdraw, user_self_organized_track) } + it{ should_not be_able_to(:index, other_self_organized_track) } + it{ should_not be_able_to(:show, other_self_organized_track) } + it{ should_not be_able_to(:restart, other_self_organized_track) } + it{ should_not be_able_to(:confirm, other_self_organized_track) } + it{ should_not be_able_to(:withdraw, other_self_organized_track) } + + it{ should be_able_to(:edit, user_self_organized_track) } + it{ should be_able_to(:update, user_self_organized_track) } + it{ should_not be_able_to(:edit, accepted_user_self_organized_track) } + it{ should_not be_able_to(:update, accepted_user_self_organized_track) } + it{ should_not be_able_to(:edit, confirmed_user_self_organized_track) } + it{ should_not be_able_to(:update, confirmed_user_self_organized_track) } + it{ should_not be_able_to(:edit, other_self_organized_track) } + it{ should_not be_able_to(:update, other_self_organized_track) } end end end diff --git a/spec/models/admin_ability_spec.rb b/spec/models/admin_ability_spec.rb index 16456a19..126d32de 100644 --- a/spec/models/admin_ability_spec.rb +++ b/spec/models/admin_ability_spec.rb @@ -44,7 +44,7 @@ describe 'User with admin role' do let!(:my_event_schedule) { create(:event_schedule, schedule: my_schedule) } let!(:other_event_schedule) { create(:event_schedule, schedule: other_schedule) } - let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program) } + let!(:my_self_organized_track) { create(:track, :self_organized, program: my_conference.program, state: 'confirmed') } context 'user #is_admin?' do let(:venue) { my_conference.venue } @@ -505,6 +505,8 @@ describe 'User with admin role' do it{ should be_able_to(:show, my_conference.program) } it{ should be_able_to(:update, new_track) } it{ should be_able_to(:manage, my_self_organized_track) } + it{ should_not be_able_to(:edit, my_self_organized_track) } + it{ should_not be_able_to(:update, my_self_organized_track) } it_behaves_like 'user with any role' it_behaves_like 'user with non-organizer role', 'track_organizer' diff --git a/spec/models/conference_spec.rb b/spec/models/conference_spec.rb index 91c06140..26070134 100755 --- a/spec/models/conference_spec.rb +++ b/spec/models/conference_spec.rb @@ -606,8 +606,8 @@ describe Conference do describe 'tracks_distribution' do before do subject.email_settings = create(:email_settings) - @track_one = create(:track, name: 'Track One', color: '#000000') - @track_two = create(:track, name: 'Track Two', color: '#ffffff') + @track_one = create(:track, name: 'Track One', color: '#000000', program: subject.program) + @track_two = create(:track, name: 'Track Two', color: '#ffffff', program: subject.program) end describe '#tracks_distribution' do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index b723d151..3ada61ca 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -96,6 +96,40 @@ describe Event do end end end + + describe '#acceptable_track' do + context 'is valid' do + it 'when the track belong to the same program, is confirmed and is included in the cfp' do + track = create(:track, state: 'confirmed', cfp_active: true, program: conference.program) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq true + end + end + + context 'is invalid' do + it 'when the track doesn\'t have the same program' do + track = create(:track, state: 'confirmed', cfp_active: true) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq false + expect(event.errors[:track]).to eq ['is invalid'] + end + + it 'when the track is unconfirmed' do + track = create(:track, cfp_active: true, program: conference.program) + allow(track).to receive(:confirmed?).and_return(false) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq false + expect(event.errors[:track]).to eq ['is invalid'] + end + + it 'when the track isn\'t included in the cfp' do + track = create(:track, state: 'confirmed', cfp_active: false, program: conference.program) + event = build(:event, program: conference.program, track: track) + expect(event.valid?).to eq false + expect(event.errors[:track]).to eq ['is invalid'] + end + end + end end describe '#comments_count' do diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 840641cb..4853f31d 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -8,6 +8,7 @@ describe Track do describe 'association' do it { is_expected.to belong_to(:program) } it { is_expected.to belong_to(:submitter).class_name('User') } + it { is_expected.to belong_to(:room) } it { is_expected.to have_many(:events) } end @@ -23,23 +24,99 @@ describe Track do it { is_expected.to allow_value('My_track_name').for(:short_name) } it { is_expected.to_not allow_value('My track name').for(:short_name) } it { is_expected.to validate_uniqueness_of(:short_name).scoped_to(:program_id) } + it { is_expected.to validate_presence_of(:state) } + it { is_expected.to validate_inclusion_of(:state).in_array(%w[new to_accept accepted confirmed to_reject rejected canceled withdrawn]) } + it { is_expected.to validate_inclusion_of(:cfp_active).in_array([true, false]) } - context 'when self-organized' do + context 'when self_organized_and_accepted_or_confirmed? returns true' do before :each do - allow(subject).to receive(:self_organized?).and_return(true) + allow(subject).to receive(:self_organized_and_accepted_or_confirmed?).and_return(true) end - it { is_expected.to validate_presence_of(:state) } - it { is_expected.to validate_inclusion_of(:cfp_active).in_array([true, false]) } + it { is_expected.to validate_presence_of(:start_date) } + it { is_expected.to validate_presence_of(:end_date) } + it { is_expected.to validate_presence_of(:room) } end - context 'when regular' do + context 'when self_organized_and_accepted_or_confirmed? returns false' do before :each do - allow(subject).to receive(:self_organized?).and_return(false) + allow(subject).to receive(:self_organized_and_accepted_or_confirmed?).and_return(false) end - it { is_expected.to_not validate_presence_of(:state) } - it { is_expected.to_not validate_inclusion_of(:cfp_active) } + it { is_expected.to_not validate_presence_of(:start_date) } + it { is_expected.to_not validate_presence_of(:end_date) } + it { is_expected.to_not validate_presence_of(:room) } + end + + describe '#valid_dates' do + before :each do + @conference = create(:conference, start_date: 1.day.ago, end_date: 2.days.from_now) + end + + context 'is valid' do + it 'when the track\'s start date is before it\'s end date and between the conference start/end dates' do + track = build(:track, start_date: Date.today, end_date: Date.tomorrow, program: @conference.program) + expect(track.valid?).to eq true + end + end + + context 'is invalid' do + it 'when the track\'s start date is before the conference\'s start date' do + track = build(:track, start_date: 2.days.ago, end_date: Date.tomorrow, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:start_date]).to eq ["can't be before the conference start date (#{1.day.ago.to_date})"] + end + + it 'when the track\'s end date is before the conference\'s start date' do + track = build(:track, start_date: 3.days.ago, end_date: 2.days.ago, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:end_date]).to eq ["can't be before the conference start date (#{1.day.ago.to_date})"] + end + + it 'when the track\'s start date is after the conference\'s end date' do + track = build(:track, start_date: 3.days.from_now, end_date: 4.days.from_now, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:start_date]).to eq ["can't be after the conference end date (#{2.days.from_now.to_date})"] + end + + it 'when the track\'s end date is after the conference\'s end date' do + track = build(:track, start_date: Date.today, end_date: 3.days.from_now, program: @conference.program) + expect(track.valid?).to eq false + expect(track.errors[:end_date]).to eq ["can't be after the conference end date (#{2.days.from_now.to_date})"] + end + + it 'when the track\'s start date is after it\'s end date' do + track = build(:track, start_date: 1.day.from_now, end_date: 1.day.ago) + expect(track.valid?).to eq false + expect(track.errors[:start_date]).to eq ['can\'t be after the end date'] + end + end + end + + describe '#valid_room' do + before :each do + @conference = create(:conference) + @conference.venue = create(:venue, name: 'The venue') + end + + context 'is valid' do + it 'when the track\'s room belongs to the venue of the conference' do + room = create(:room, venue: @conference.venue) + track = build(:track, :self_organized, state: 'accepted', program: @conference.program, room: room) + expect(track.valid?).to eq true + end + end + + context 'is invalid' do + it 'when the track\'s room doesn\'t belong to the venue of the track\'s conference' do + other_conference = create(:conference) + other_conference.venue = create(:venue) + room = create(:room, venue: other_conference.venue) + track = build(:track, :self_organized, state: 'accepted', program: @conference.program, room: room) + expect(track.valid?).to eq false + expect(track.errors[:room]).to eq ['must be a room of The venue'] + end + end end end @@ -54,4 +131,226 @@ describe Track do expect(track.self_organized?).to eq false end end + + describe '#transition_possible?' do + shared_examples 'transition_possible?' do |state, transition, expected| + it "returns #{expected} for #{transition} event, when the track's state is #{state}}" do + my_self_organized_track = create(:track, :self_organized, state: state) + expect(my_self_organized_track.transition_possible?(transition.to_sym)).to eq expected + end + end + + states = [:new, :to_accept, :accepted, :confirmed, :to_reject, :rejected, :canceled, :withdrawn] + transitions = [:restart, :to_accept, :accept, :confirm, :to_reject, :reject, :cancel, :withdraw] + + states_transitions = { new: { restart: false, to_accept: true, accept: true, confirm: false, to_reject: true, reject: true, cancel: false, withdraw: true }, + to_accept: { restart: false, to_accept: false, accept: true, confirm: false, to_reject: false, reject: false, cancel: true, withdraw: true }, + accepted: { restart: false, to_accept: false, accept: false, confirm: true, to_reject: false, reject: false, cancel: true, withdraw: true }, + confirmed: { restart: false, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: true, withdraw: true }, + to_reject: { restart: false, to_accept: false, accept: false, confirm: false, to_reject: false, reject: true, cancel: true, withdraw: true }, + rejected: { restart: true, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: false, withdraw: false }, + canceled: { restart: true, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: false, withdraw: false }, + withdrawn: { restart: true, to_accept: false, accept: false, confirm: false, to_reject: false, reject: false, cancel: false, withdraw: false } } + + states.each do |state| + transitions.each do |transition| + it_behaves_like 'transition_possible?', state, transition, states_transitions[state.to_sym][transition.to_sym] + end + end + end + + describe '#assign_role_to_submitter' do + before :each do + Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create + @submitter = self_organized_track.submitter + end + + it 'gives the role of the track organizer to the submitter of the track' do + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq false + self_organized_track.assign_role_to_submitter + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq true + end + + it 'is executed when the track is confirmed' do + self_organized_track.state = 'accepted' + self_organized_track.save! + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq false + self_organized_track.confirm + expect(@submitter.has_role?(:track_organizer, self_organized_track)).to eq true + end + end + + describe '#revoke_role_and_cleanup' do + before :each do + Role.where(name: 'track_organizer', resource: self_organized_track).first_or_create + @a_track_organizer = create(:user) + self_organized_track.state = 'confirmed' + self_organized_track.cfp_active = true + self_organized_track.save! + @a_track_organizer.add_role 'track_organizer', self_organized_track + @an_event_of_the_track = create(:event, program: self_organized_track.program, track: self_organized_track) + end + + it 'revokes the role of the track organizer' do + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq true + self_organized_track.revoke_role_and_cleanup + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false + end + + it 'removes the track from the events that have it set' do + expect(@an_event_of_the_track.track).to eq self_organized_track + self_organized_track.revoke_role_and_cleanup + @an_event_of_the_track.reload + expect(@an_event_of_the_track.track).to eq nil + end + + it 'is executed when the track is canceled' do + self_organized_track.state = 'confirmed' + self_organized_track.save! + self_organized_track.cancel + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false + @an_event_of_the_track.reload + expect(@an_event_of_the_track.track).to eq nil + end + + it 'is executed when the track is withdrawn' do + self_organized_track.withdraw + expect(@a_track_organizer.has_role?(:track_organizer, self_organized_track)).to eq false + @an_event_of_the_track.reload + expect(@an_event_of_the_track.track).to eq nil + end + end + + describe '#accepted?' do + context 'returns true' do + it 'when the state is "accepted"' do + self_organized_track.state = 'accepted' + self_organized_track.save! + expect(self_organized_track.accepted?).to eq true + end + end + + context 'returns false' do + %w[new to_accept confirmed to_reject rejected canceled withdrawn].each do |state| + it "when the state is \"#{state}\"" do + self_organized_track.state = state + self_organized_track.save! + expect(self_organized_track.accepted?).to eq false + end + end + end + end + + describe '#confirmed?' do + context 'returns true' do + it 'when the state is "confirmed"' do + self_organized_track.state = 'confirmed' + self_organized_track.save! + expect(self_organized_track.confirmed?).to eq true + end + end + + context 'returns false' do + %w[new to_accept accepted to_reject rejected canceled withdrawn].each do |state| + it "when the state is \"#{state}\"" do + self_organized_track.state = state + self_organized_track.save! + expect(self_organized_track.confirmed?).to eq false + end + end + end + end + + # accepted? and confirmed? are mutually exclusive (they can't be both true) + describe '#self_organized_and_accepted_or_confirmed?' do + context 'returns true' do + context 'when self_organized? returns true' do + before :each do + allow(track).to receive(:self_organized?).and_return(true) + end + + context 'accepted? returns true and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(true) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq true } + end + + context 'accepted? returns false and confirmed? returns true' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(true) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq true } + end + end + end + + context 'returns false' do + context 'when self_organized? returns true' do + before :each do + allow(track).to receive(:self_organized?).and_return(true) + end + + context 'accepted? returns false and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + end + + context 'when self_organized? returns false' do + before :each do + allow(track).to receive(:self_organized?).and_return(false) + end + + context 'accepted? returns false and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + + context 'accepted? returns true and confirmed? returns false' do + before :each do + allow(track).to receive(:accepted?).and_return(true) + allow(track).to receive(:confirmed?).and_return(false) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + + context 'accepted? returns false and confirmed? returns true' do + before :each do + allow(track).to receive(:accepted?).and_return(false) + allow(track).to receive(:confirmed?).and_return(true) + end + + it { expect(track.self_organized_and_accepted_or_confirmed?).to eq false } + end + end + end + end + + describe '#create_organizer_role' do + it 'creates the role of the track organizer' do + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track)).to eq nil + self_organized_track.send(:create_organizer_role) + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track).description).to eq 'For the organizers of the Track' + end + + it 'is executed when the track is accepted' do + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track)).to eq nil + self_organized_track.accept + expect(Role.find_by(name: 'track_organizer', resource: self_organized_track).description).to eq 'For the organizers of the Track' + end + end end