removes scenarios and alternate dates

pull/369/head
Jens Ulferts 11 years ago
parent d3974d429c
commit b541755eda
  1. 2
      app/assets/javascripts/timelines.js
  2. 35
      app/controllers/api/v2/scenarios_controller.rb
  3. 7
      app/controllers/planning_elements_controller.rb
  4. 92
      app/controllers/scenarios_controller.rb
  5. 14
      app/helpers/planning_elements_helper.rb
  6. 6
      app/helpers/timelines_helper.rb
  7. 54
      app/models/alternate_date.rb
  8. 1
      app/models/journal.rb
  9. 2
      app/models/legacy_journal.rb
  10. 9
      app/models/permitted_params.rb
  11. 125
      app/models/planning_element.rb
  12. 48
      app/models/planning_element_scenario.rb
  13. 3
      app/models/project.rb
  14. 28
      app/models/scenario.rb
  15. 16
      app/models/work_package.rb
  16. 9
      app/views/api/v2/projects/_project.api.rsb
  17. 37
      app/views/planning_elements/_form.html.erb
  18. 11
      app/views/planning_elements/_planning_element.html.erb
  19. 2
      app/views/projects/settings/_timelines.html.erb
  20. 27
      app/views/scenarios/_form.html.erb
  21. 61
      app/views/scenarios/_index.html.erb
  22. 29
      app/views/scenarios/confirm_destroy.html.erb
  23. 24
      app/views/scenarios/edit.html.erb
  24. 24
      app/views/scenarios/new.html.erb
  25. 23
      config/locales/de.yml
  26. 20
      config/locales/en.yml
  27. 5
      config/routes.rb
  28. 48
      db/migrate/20130828093647_remove_alternate_dates_and_scenarios.rb
  29. 21
      features/step_definitions/timelines_given_steps.rb
  30. 27
      lib/open_project/journal_formatter/scenario_date.rb
  31. 4
      lib/redmine.rb
  32. 171
      spec/controllers/api/v2/scenarios_controller_spec.rb
  33. 94
      spec/controllers/scenarios_controller_spec.rb
  34. 34
      spec/factories/alternate_date_factory.rb
  35. 19
      spec/factories/scenario_factory.rb
  36. 5
      spec/factories/timeline_factory.rb
  37. 98
      spec/lib/journal_formatter/scenario_date_spec.rb
  38. 156
      spec/models/alternate_date_spec.rb
  39. 26
      spec/models/permitted_params_spec.rb
  40. 62
      spec/models/planning_element_scenario_spec.rb
  41. 417
      spec/models/planning_element_spec.rb
  42. 97
      spec/models/scenario_spec.rb
  43. 27
      spec/models/timelines_project_spec.rb
  44. 41
      spec/support/shared/a_model_with_non_negative_duration.rb
  45. 98
      spec/views/api/v2/planning_elements/_planning_element_api_rsb_spec.rb
  46. 1
      spec/views/api/v2/planning_elements/destroy_api_rsb_spec.rb
  47. 2
      spec/views/api/v2/planning_elements/index_api_rsb_spec.rb
  48. 2
      spec/views/api/v2/planning_elements/show_api_rsb_spec.rb
  49. 53
      spec/views/api/v2/projects/_project_api_rsb_spec.rb
  50. 75
      spec/views/api/v2/scenarios/_scenario_api_rsb_spec.rb
  51. 86
      spec/views/api/v2/scenarios/index_api_rsb_spec.rb
  52. 56
      spec/views/api/v2/scenarios/show_api_rsb_spec.rb

@ -215,8 +215,6 @@ Timeline = {
case 'historical':
value = this.options.compare_to_historical_one;
break;
case 'scenario':
return this.die(this.i18n('timelines.errors.not_implemented'));
default:
return this.die(this.i18n('timelines.errors.report_comparison'));
}

@ -1,35 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module Api
module V2
class ScenariosController < ScenariosController
include ::Api::V2::ApiController
def index
@scenarios = @project.scenarios
respond_to do |format|
format.api
end
end
def show
@scenario = @project.scenarios.find(params[:id])
respond_to do |format|
format.api
end
end
end
end
end

@ -301,17 +301,12 @@ class PlanningElementsController < ApplicationController
end
# Helpers
helper_method :include_journals?, :include_scenarios?
helper_method :include_journals?
def include_journals?
params[:include].tap { |i| i.present? && i.include?("journals") }
end
def include_scenarios?
!params[:exclude].tap { |i| i.present? && i.include?("scenarios") }
end
def default_breadcrumb
l('timelines.project_menu.planning_elements')
end

@ -1,92 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class ScenariosController < ApplicationController
unloadable
helper :timelines
before_filter :disable_api
before_filter :find_project_by_project_id
before_filter :authorize
accept_key_auth :index, :show
def index
@scenarios = @project.scenarios
respond_to do |format|
format.html { render_404 }
end
end
def show
@scenario = @project.scenarios.find(params[:id])
respond_to do |format|
format.html { render_404 }
end
end
# Admin actions
def new
@scenario = @project.scenarios.new(:name => l('timelines.new_scenario'))
end
def create
@scenario = @project.scenarios.new(permitted_params.scenario)
if @scenario.save
flash[:notice] = l(:notice_successful_create)
redirect_to project_settings_path
else
render :action => 'new'
end
end
def edit
@scenario = @project.scenarios.find(params[:id])
end
def update
@scenario = @project.scenarios.find(params[:id])
if @scenario.update_attributes(permitted_params.scenario)
flash[:notice] = l(:notice_successful_update)
redirect_to project_settings_path
else
render :action => 'edit'
end
end
def confirm_destroy
@scenario = @project.scenarios.find(params[:id])
end
def destroy
@scenario = @project.scenarios.find(params[:id])
@scenario.destroy
flash[:notice] = l(:notice_successful_delete)
redirect_to project_settings_path
end
protected
def project_settings_path
url_for(:controller => '/projects', :action => 'settings', :tab => 'timelines', :id => @project)
end
helper_method :project_settings_path
def default_breadcrumb
[render_to_string(:inline => "<%= link_to(l(:label_settings), project_settings_path) %>").html_safe,
l('timelines.scenarios')]
end
end

@ -53,20 +53,6 @@ module PlanningElementsHelper
api.planning_element_status(:id => status.id, :name => status.name)
end
if include_scenarios?
scenarios = planning_element.scenarios
if scenarios.present?
api.array(:scenarios, :size => scenarios.size) do
scenarios.each do |pe_scenario|
api.scenario(:id => pe_scenario.id, :name => pe_scenario.name) do
api.start_date(pe_scenario.start_date.to_formatted_s(:db)) unless pe_scenario.start_date.nil?
api.end_date(pe_scenario.due_date.to_formatted_s(:db)) unless pe_scenario.due_date.nil?
end
end
end
end
end
api.planning_element_status_comment(planning_element.planning_element_status_comment)
if include_journals?

@ -183,8 +183,7 @@ module TimelinesHelper
}
.timelines-color-properties label,
.timelines-pet-properties label,
.timelines-pt-properties label,
.timelines-scenario-properties label {
.timelines-pt-properties label {
display: inline-block;
width: 10em;
}
@ -193,9 +192,6 @@ module TimelinesHelper
.timelines-pt-properties .timelines-value {
margin-left: 0.4em;
}
td.timelines-scenario-name {
font-weight: bold;
}
.contextual.timelines-for_previous_heading {
margin-top: -2.6em;

@ -1,54 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class AlternateDate < ActiveRecord::Base
unloadable
self.table_name = 'alternate_dates'
belongs_to :planning_element, :class_name => "PlanningElement",
:foreign_key => 'planning_element_id'
belongs_to :scenario, :class_name => "Scenario",
:foreign_key => 'scenario_id'
# history-related = historic
scope :historic, :conditions => "#{self.table_name}.scenario_id IS NULL"
# scenario-related = scenaric
scope :scenaric, :conditions => "#{self.table_name}.scenario_id IS NOT NULL"
validates_presence_of :start_date, :due_date, :planning_element
delegate :planning_element_type, :planning_element_type_id, :is_milestone?, :to => :planning_element
attr_accessible :start_date, :due_date
validate do
if self.due_date and self.start_date and self.due_date < self.start_date
errors.add :due_date, :greater_than_start_date
end
if self.planning_element.present? and self.is_milestone?
if self.due_date and self.start_date and self.start_date != self.due_date
errors.add :due_date, :not_start_date
end
end
end
def duration
if start_date >= due_date
1
else
due_date - start_date + 1
end
end
end

@ -19,7 +19,6 @@ class Journal < ActiveRecord::Base
register_journal_formatter :diff, OpenProject::JournalFormatter::Diff
register_journal_formatter :attachment, OpenProject::JournalFormatter::Attachment
register_journal_formatter :custom_field, OpenProject::JournalFormatter::CustomField
register_journal_formatter :scenario_date, OpenProject::JournalFormatter::ScenarioDate
attr_accessible :journable_type, :journable_id, :activity_type, :version, :notes, :user_id

@ -15,7 +15,6 @@ require 'redmine/acts/journalized/format_hooks'
require 'open_project/journal_formatter/diff'
require 'open_project/journal_formatter/attachment'
require 'open_project/journal_formatter/custom_field'
require 'open_project/journal_formatter/scenario_date'
# The ActiveRecord model representing journals.
class LegacyJournal < ActiveRecord::Base
@ -41,7 +40,6 @@ class LegacyJournal < ActiveRecord::Base
register_journal_formatter :diff, OpenProject::JournalFormatter::Diff
register_journal_formatter :attachment, OpenProject::JournalFormatter::Attachment
register_journal_formatter :custom_field, OpenProject::JournalFormatter::CustomField
register_journal_formatter :scenario_date, OpenProject::JournalFormatter::ScenarioDate
# "touch" the journaled object on creation
after_create :touch_journaled_after_creation

@ -73,10 +73,6 @@ class PermittedParams < Struct.new(:params, :user)
params.require(:planning_element_type).permit(*self.class.permitted_attributes[:planning_element_type_move])
end
def scenario
params.require(:scenario).permit(*self.class.permitted_attributes[:scenario])
end
def planning_element
params.require(:planning_element).permit(*self.class.permitted_attributes[:planning_element])
end
@ -211,7 +207,6 @@ class PermittedParams < Struct.new(:params, :user)
:description,
:start_date,
:due_date,
{ scenarios: [:id, :start_date, :due_date] },
:note,
:planning_element_type_id,
:planning_element_status_comment,
@ -234,10 +229,6 @@ class PermittedParams < Struct.new(:params, :user)
:type_ids => [],
:reported_project_status_ids => []
],
:scenario => [
:name,
:description
],
:type => [
:name,
:is_in_roadmap,

@ -14,86 +14,15 @@ class PlanningElement < WorkPackage
include ActiveModel::ForbiddenAttributesProtection
has_many :alternate_dates, :class_name => "AlternateDate",
:foreign_key => 'planning_element_id',
:autosave => true,
:dependent => :delete_all
accepts_nested_attributes_for_apis_for :parent,
:planning_element_status,
:type,
:project
# This SQL only works when there are no two updates in the same
# millisecond. As soon as updates happen in rapid succession, multiple
# instances of one planning element are returned.
SQL_FOR_AT = {
:select => "#{PlanningElement.quoted_table_name}.id,
#{PlanningElement.quoted_table_name}.subject,
#{PlanningElement.quoted_table_name}.description,
#{PlanningElement.quoted_table_name}.planning_element_status_comment,
#{AlternateDate.quoted_table_name }.start_date,
#{AlternateDate.quoted_table_name }.due_date,
#{PlanningElement.quoted_table_name}.parent_id,
#{PlanningElement.quoted_table_name}.project_id,
#{PlanningElement.quoted_table_name}.responsible_id,
#{PlanningElement.quoted_table_name}.type_id,
#{PlanningElement.quoted_table_name}.planning_element_status_id,
#{PlanningElement.quoted_table_name}.created_at,
#{PlanningElement.quoted_table_name}.deleted_at,
#{AlternateDate.quoted_table_name }.updated_at",
:joins => "LEFT JOIN (
SELECT
#{AlternateDate.quoted_table_name}.planning_element_id,
MAX(#{AlternateDate.quoted_table_name}.updated_at) AS updated_at
FROM #{AlternateDate.quoted_table_name}
WHERE
#{AlternateDate.quoted_table_name}.created_at <= ?
GROUP BY
#{AlternateDate.quoted_table_name}.planning_element_id
) AS alternate_dates_sub
ON #{PlanningElement.quoted_table_name}.id = alternate_dates_sub.planning_element_id
INNER JOIN
#{AlternateDate.quoted_table_name}
ON #{AlternateDate.quoted_table_name}.planning_element_id = alternate_dates_sub.planning_element_id
AND #{AlternateDate.quoted_table_name}.updated_at = alternate_dates_sub.updated_at"
}
scope :at_time, lambda { |time|
{:select => SQL_FOR_AT[:select],
:conditions => ["(#{PlanningElement.quoted_table_name}.deleted_at IS NULL
OR #{PlanningElement.quoted_table_name}.deleted_at >= ?)", time],
:joins => sanitize_sql([SQL_FOR_AT[:joins], time]),
:readonly => true
}
}
scope :for_projects, lambda { |projects|
{:conditions => {:project_id => projects}}
}
def append_scenario_dates_to_journal
changes = {}
alternate_dates.each do |d|
if d.scenario.present? && (!(alternate_date_changes = d.changes).empty? || d.marked_for_destruction?)
["start_date", "due_date"].each do |field|
old_value = if (scenario_changes = alternate_date_changes["scenario_id"])
scenario_changes.first.nil? ? nil : d.send(field)
else
alternate_date_changes[field].nil? ? d.send(field) : alternate_date_changes[field].first
end
new_value = d.marked_for_destruction? ? nil : d.send(field)
changes.merge!({ "scenario_#{d.scenario.id}_#{field}" => [old_value, new_value] }) unless new_value == old_value
end
end
end
journal_changes.append_changes!(changes)
end
before_save :append_scenario_dates_to_journal
validates_presence_of :subject, :project
validates_length_of :subject, :maximum => 255, :unless => lambda { |e| e.subject.blank? }
@ -118,58 +47,4 @@ class PlanningElement < WorkPackage
end
end
def all_scenarios
project.scenarios.sort_by(&:id).map do |scenario|
alternate_date = alternate_dates.to_a.find { |a| a.scenario_id.to_s == scenario.id.to_s }
alternate_date ||= alternate_dates.build.tap { |ad| ad.scenario_id = scenario.id }
PlanningElementScenario.new(alternate_date)
end
end
def scenarios
alternate_dates.scenaric.sort_by(&:scenario_id).map do |alternate_date|
PlanningElementScenario.new(alternate_date)
end
end
# Expecting pe_scenarios to be an Array of Hashes or a Hash of Hashes with
# arbitrary keys following the following schema:
#
# {
# 'id' => 1,
# 'start_date => '2012-01-01',
# 'due_date' => '2012-01-03'
# }
#
# The id attribute is required. If both date fields are empty or missing, the
# alternate date will be deleted. The alternate date will also be deleted,
# when the "_destroy" key is present and set to "1".
#
# Other attributes will be silently ignored.
#
def scenarios=(pe_scenarios)
pe_scenarios = pe_scenarios.values if pe_scenarios.is_a? Hash
pe_scenarios.each do |pe_scenario|
alternate_date = alternate_dates.to_a.find { |date| date.scenario_id.to_s == pe_scenario['id'].to_s }
unless alternate_date
if self.new_record?
alternate_date = AlternateDate.new.tap { |ad| ad.scenario_id = pe_scenario['id'] }
alternate_date.planning_element = self
alternate_dates << alternate_date
else
alternate_date = alternate_dates.build.tap { |ad| ad.scenario_id = pe_scenario['id'] }
end
end
if (pe_scenario['start_date'].blank? and pe_scenario['due_date'].blank?) or
pe_scenario['_destroy'] == '1'
alternate_date.mark_for_destruction
else
alternate_date.attributes = {'start_date' => pe_scenario['start_date'],
'due_date' => pe_scenario['due_date']}
end
end
end
end

@ -1,48 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class PlanningElementScenario
unloadable
attr_accessor :alternate_date
def initialize(alternate_date)
raise ArgumentError, 'Please pass an actual alternate date' if alternate_date.nil?
@alternate_date = alternate_date
end
delegate :start_date, :start_date=, :due_date, :due_date=,
:scenario, :scenario_id,
:duration, :planning_element,
:valid?, :errors,
:to => :alternate_date
delegate :name, :id, :to_param, :to => :scenario
def _destroy
false
end
def ==(other)
other.is_a?(self.class) &&
other.alternate_date == @alternate_date
end
def eql?(other)
other.class == self.class &&
other.alternate_date == @alternate_date
end
def hash
@alternate_date.hash
end
end

@ -116,9 +116,6 @@ class Project < ActiveRecord::Base
:dependent => :destroy
has_many :planning_elements, :class_name => "::PlanningElement",
:dependent => :destroy
has_many :scenarios, :class_name => "::Scenario",
:dependent => :destroy
has_many :reportings_via_source, :class_name => "::Reporting",
:foreign_key => 'project_id',

@ -1,28 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class Scenario < ActiveRecord::Base
unloadable
self.table_name = 'scenarios'
include ActiveModel::ForbiddenAttributesProtection
belongs_to :project
has_many :alternate_dates, :class_name => 'AlternateDate',
:foreign_key => 'scenario_id',
:dependent => :delete_all
validates_presence_of :name, :project
validates_length_of :name, :maximum => 255, :unless => lambda { |e| e.name.blank? }
end

@ -56,8 +56,7 @@ class WorkPackage < ActiveRecord::Base
before_save :store_former_parent_id
include OpenProject::NestedSet::WithRootIdScope
after_save :reschedule_following_issues,
:update_parent_attributes,
:create_alternate_date
:update_parent_attributes
after_move :remove_invalid_relations,
:recalculate_attributes_for_former_parent
@ -142,7 +141,6 @@ class WorkPackage < ActiveRecord::Base
register_on_journal_formatter :plaintext, :subject,
:planning_element_status_comment,
:responsible_id
register_on_journal_formatter :scenario_date, /^scenario_(\d+)_(start|due)_date$/
# acts_as_journalized will create an initial journal on wp creation
# and touch the journaled object:
@ -613,16 +611,4 @@ class WorkPackage < ActiveRecord::Base
time_entries.build(attributes)
end
def create_alternate_date
# This is a hack.
# It is required as long as alternate dates exist/are not moved up to work_packages.
# Its purpose is to allow for setting the after_save filter in the correct order
# before acts as journalized and the cleanup method reload_lock_and_timestamps.
return true unless respond_to?(:alternate_dates)
if start_date_changed? or due_date_changed?
alternate_dates.create(:start_date => start_date, :due_date => due_date)
end
end
end

@ -59,15 +59,6 @@ api.project do
end
end
scenarios = project.scenarios
if scenarios.present?
api.array :scenarios, :size => scenarios.size do
scenarios.each do |scenario|
api.scenario(:id => scenario.id, :name => scenario.name)
end
end
end
project_associations = project.project_associations.visible
if project_associations.present?
api.array :project_associations, :size => project_associations.size do

@ -93,43 +93,6 @@ See doc/COPYRIGHT.rdoc for more details.
</td>
<td>&nbsp;</td>
</tr>
<% planning_element.all_scenarios.each do |pe_scenario| %>
<%= fields_for 'planning_element[scenarios][]', pe_scenario do |scenario_fields| %>
<% id_base = "planning_element_scenarios_#{scenario_fields.object.id}" %>
<tr class="timelines-scenaric-dates">
<th>
<%= scenario_fields.hidden_field :id %>
<%=h scenario_fields.object.name %>:
</th>
<td>
<label class="hidden-for-sighted" for="<%= id_base %>_start_date">
<%=h scenario_fields.object.name %>
<%= PlanningElement.human_attribute_name(:start_date) %>
</label>
<%= scenario_fields.text_field :start_date, date_input_options %><%= calendar_for("#{id_base}_start_date") if planning_element.leaf? %>
-
<label class="hidden-for-sighted" for="<%= id_base %>_due_date">
<%=h scenario_fields.object.name %>
<%= PlanningElement.human_attribute_name(:due_date) %>
</label>
<%= scenario_fields.text_field :due_date, date_input_options %><%= calendar_for("#{id_base}_due_date") if planning_element.leaf? %>
<%= scenario_fields.check_box :_destroy %>
<label for="<%= id_base %>__destroy">
<%= t(:button_delete) %>
<span class="hidden-for-sighted">
<%=h scenario_fields.object.name %>
</span>
</label>
&nbsp;
</td>
</tr>
<% end %>
<% end %>
</table>
</div>
</div>

@ -95,17 +95,6 @@ See doc/COPYRIGHT.rdoc for more details.
<%= format_date planning_element.start_date, format_date_options %> - <%= format_date planning_element.due_date, format_date_options %>
</td>
</tr>
<% planning_element.scenarios.each do |pe_scenario| %>
<tr>
<th>
<%=h pe_scenario.name %>:
</th>
<td>
<%= format_date pe_scenario.start_date, format_date_options %> - <%= format_date pe_scenario.due_date, format_date_options %>
</td>
</tr>
<% end %>
</table>
</div>
</div>

@ -59,5 +59,3 @@ See doc/COPYRIGHT.rdoc for more details.
<% end %>
<br/>
<%= render :partial => 'scenarios/index' %>

@ -1,27 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2013 the OpenProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= error_messages_for 'scenario' %>
<fieldset class="timelines-scenario-properties">
<legend><%= l('timelines.properties') %></legend>
<p>
<label for="scenario_name">
<%= Scenario.human_attribute_name(:name) %>
</label>
<%= f.text_field :name, :class => 'autofocus' %>
</p>
</fieldset>
<%= submit_tag l(:button_save) %>
<%= link_to l(:button_cancel), project_settings_path %>

@ -1,61 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2013 the OpenProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%#
Used from app/views/projects/settings/_timelines.html.erb
%>
<h3><%= Scenario.model_name.human %></h3>
<div class='contextual timelines-for_previous_heading'>
<%= link_to(l("timelines.new_scenario"),
new_project_scenario_path(@project),
:title => l("timelines.new_scenario"),
:class => 'icon icon-add') %>
</div>
<table class='list'>
<thead>
<tr>
<th colspan="2"><%= Scenario.human_attribute_name(:name) %></th>
<th><%= Scenario.human_attribute_name(:created_at) %></th>
<th><%= Scenario.human_attribute_name(:updated_at) %></th>
<th>&nbsp;<!-- Actions --></th>
</tr>
</thead>
<tbody>
<% @project.scenarios.each do |scenario| %>
<tr class="<%= cycle('odd', 'even', :name => "scenario_table") %>">
<td class="timelines-scenario-name">
<%=h scenario.name %>
</td>
<td class="timelines-scenario-description">
<%= l('timelines.used_in_x_planning_elements', :count => scenario.alternate_dates.count) %>
</td>
<td class="timelines-scenario-created_at">
<%= timelines_time_tag(scenario.created_at) %>
</td>
<td class="timelines-scenario-updated_at">
<%= timelines_time_tag(scenario.updated_at) %>
</td>
<td class="timelines-scenario-actions">
<%= link_to l(:button_edit),
edit_project_scenario_path(@project, scenario),
:class => 'icon icon-edit' %>
<%= link_to l(:button_delete),
confirm_destroy_project_scenario_path(@project, scenario),
:class => 'icon icon-del' %>
</td>
</tr>
<% end %>
</tbody>
</table>

@ -1,29 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2013 the OpenProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= header_tags %>
<h2>
<%=h @scenario.name %>
</h2>
<%= form_for :scenario,
:url => project_scenario_url(@project, @scenario),
:html => {:method => 'delete'} do |f| %>
<div class='flash warning'>
<%= l('timelines.really_delete_scenario') %>
</div>
<%= submit_tag l(:button_delete) %>
<%= link_to l(:button_cancel), project_settings_path %>
<% end %>

@ -1,24 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2013 the OpenProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= header_tags %>
<h2>
<%=h @scenario.name %>
</h2>
<%= form_for :scenario,
:url => project_scenario_url(@project, @scenario),
:html => {:method => 'put'} do |f| %>
<%= render :partial => 'form',
:locals => {:f => f, :scenario => @scenario} %>
<% end %>

@ -1,24 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2013 the OpenProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= header_tags %>
<h2>
<%= l('timelines.new_scenario') %>
</h2>
<%= form_for :scenario,
:url => project_scenarios_url(@project),
:html => {:method => 'post'} do |f| %>
<%= render :partial => 'form',
:locals => {:f => f, :scenario => @scenario} %>
<% end %>

@ -92,8 +92,6 @@ de:
name: "Name"
hexcode: "Hex-Code"
planning_element:
alternate_dates:
due_date: "Abschlussdatum des alternativen Datums"
description: "Beschreibung"
id: "ID"
name: "Name"
@ -104,9 +102,6 @@ de:
allows_association: "Erlaubt Projekt-Abhängigkeiten"
name: "Name"
reported_project_statuses: "Gemeldeter Projekt-Status"
scenario:
created_at: "Angelegt"
name: "Name"
timeline:
name: "Name"
reporting:
@ -182,10 +177,6 @@ de:
too_short: "ist zu kurz (nicht weniger als %{count} Zeichen)"
wrong_length: "hat die falsche Länge (muss genau %{count} Zeichen haben)"
models:
alternate_date:
attributes:
due_date:
not_start_date: "ist nicht identisch mit dem Startdatum, obwohl dies bei Meilensteinen Pflicht ist"
planning_element:
attributes:
due_date:
@ -233,7 +224,6 @@ de:
type: "Typ"
planning_element: "Planungselement"
project_type: "Projekt-Typ"
scenario: "Szenario"
user: "Benutzer"
version: "Version"
wiki: "Wiki"
@ -490,9 +480,6 @@ de:
field_responsible: "Planungsverantwortlicher"
field_end_date: "Abschlussdatum"
field_deleted_at: "Gelöscht am"
field_scenario_end_date: "Szenario %{title}: Abschlussdatum"
field_scenario_start_date: "Szenario %{title}: Startdatum"
field_scenario_deleted: '(gelöschtes Szenario)'
field_planning_element_status: "Status"
# Overriding definition in core to make field_parent more generic, so that it
@ -853,9 +840,6 @@ de:
label_role_new: "Neue Rolle"
label_role_plural: "Rollen"
label_role_search: "Nach Rollen suchen"
label_scenario_due_date: "Scenario %{title}: Abgabedatum"
label_scenario_start_date: "Scenario %{title}: Startdatum"
label_scenario_deleted: '(gelöschtes Scenario)'
label_scm: "Versionskontrollsystem"
label_search: "Suche"
label_search_titles_only: "Nur Titel durchsuchen"
@ -1366,7 +1350,6 @@ de:
historical: "Historisch"
none: "Kein Planungsvergleich"
relative: "Relativ"
scenario: "Szenario"
compare_relative: "Vergleiche aktuelle Planung mit vor %{timespan}"
compare_absolute: "Vergleiche aktuelle Planung mit %{date}"
compare_historical: "Vergleiche %{startdate} mit %{enddate}"
@ -1410,7 +1393,6 @@ de:
new_planning_element: "Neues Planungselement"
new_project_type: "Neuer Projekt-Typ"
new_reporting: "Neuer Statusbericht"
new_scenario: "Neues Szenario"
new_timeline: "Neuer Zeitplan-Report"
no_projects_for_reporting_available: "Es gibt keine Projekte, zu denen ein Statusbericht erstellt werden könnte."
no_right_to_view_timeline: "Sie haben nicht das Recht, den ausgewählten Zeitplan zu sehen."
@ -1474,10 +1456,6 @@ de:
really_delete_project_type: >
Möchten Sie den folgenden Projekt-Typ wirklich löschen?
Projekte dieses Typs bleiben erhalten.
really_delete_scenario: >
Möchten Sie dieses Szenario wirklich löschen?
Planungselemente werden nicht mit gelöscht - die Planungsinformationen
für dieses Szenario gehen jedoch verloren.
really_delete_timeline: >
Möchten Sie dieses Zeitplan-Report wirklich löschen?
Planungselemente dieses Reports bleiben erhalten.
@ -1490,7 +1468,6 @@ de:
really_restore_all_planning_elements: >
Möchten Sie die folgenden Planungselemente wirklich wiederherstellen?
restore_all: "Alle wiederherstellen"
scenarios: "Szenarien"
start: "Beginn"
timeline: "Zeitplan-Report"
timelines: "Zeitplan-Reporte"

@ -124,8 +124,6 @@ en:
name: Name
hexcode: Hex code
planning_element:
alternate_dates:
due_date: End date of alternate date
description: Description
id: ID
name: Name
@ -140,9 +138,6 @@ en:
name: Name
reported_project_statuses: Reported project statuses
position: Position
scenario:
created_at: Created
name: Name
timeline:
name: Name
reporting:
@ -186,10 +181,6 @@ en:
too_short: "is too short (minimum is %{count} characters)"
wrong_length: "is the wrong length (should be %{count} characters)"
models:
alternate_date:
attributes:
due_date:
not_start_date: "is not on start date, although this is required for milestones"
planning_element:
attributes:
due_date:
@ -237,7 +228,6 @@ en:
type: "Type"
planning_element: Planning element
project_type: Project type
scenario: Scenario
user: "User"
version: "Version"
wiki: "Wiki"
@ -835,9 +825,6 @@ en:
label_role_new: "New role"
label_role_plural: "Roles"
label_role_search: "Search for roles"
label_scenario_due_date: "Scenario %{title}: Due date"
label_scenario_start_date: "Scenario %{title}: Start date"
label_scenario_deleted: '(deleted scenario)'
label_scm: "SCM"
label_search: "Search"
label_search_titles_only: "Search titles only"
@ -1346,7 +1333,6 @@ en:
historical: "Historical"
none: "None"
relative: "Relative"
scenario: "Scenario"
compare_relative: "Compare current planning to %{timespan} ago"
compare_absolute: "Compare current planning to %{date}"
compare_historical: "Compare %{startdate} to %{enddate}"
@ -1389,7 +1375,6 @@ en:
new_planning_element: "New planning element"
new_project_type: "New project type"
new_reporting: "New reporting"
new_scenario: "New scenario"
new_timeline: "New timeline report"
no_projects_for_reporting_available: "There are no projects to which a reporting association can be created."
no_right_to_view_timeline: "You do not have the necessary permission to view the linked timeline."
@ -1446,10 +1431,6 @@ en:
really_delete_project_type: >
Are you sure, you want to delete the following project type?
Projects using this type will not be deleted.
really_delete_scenario: >
Are you sure, you want to delete this scenario?
Planning elements with start and end dates within this scenario will not
be deleted.
really_delete_timeline: >
Are you sure, you want to delete the following timeline report?
Planning elements shown in this timeline report will not be
@ -1463,7 +1444,6 @@ en:
really_restore_all_planning_elements: >
Are you sure, you want to restore all the following planning elements?
restore_all: Restore all
scenarios: "Scenarios"
start: "Start"
timeline: "Timeline report"
timelines: "Timeline reports"

@ -59,7 +59,6 @@ OpenProject::Application.routes.draw do
get :paginate_reported_project_statuses
end
end
resources :scenarios
resources :timelines
resources :projects do
@ -498,10 +497,6 @@ OpenProject::Application.routes.draw do
get :confirm_destroy, :on => :member
end
resources :scenarios, :controller => 'scenarios' do
get :confirm_destroy, :on => :member
end
resources :timelines, :controller => 'timelines'
resources :principals, :controller => 'timelines_principals' do

@ -0,0 +1,48 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class RemoveAlternateDatesAndScenarios < ActiveRecord::Migration
def up
drop_table(:alternate_dates)
drop_table(:scenarios)
end
def down
create_table(:scenarios) do |t|
t.column :name, :string, :null => false
t.column :description, :text
t.belongs_to :project
t.timestamps
end
add_index :scenarios, :project_id
create_table(:alternate_dates) do |t|
t.column :start_date, :date, :null => false
t.column :due_date, :date, :null => false
t.belongs_to :scenario
t.belongs_to :planning_element
t.timestamps
end
add_index :alternate_dates, :planning_element_id
add_index :alternate_dates, :scenario_id
add_index :alternate_dates,
[:updated_at, :planning_element_id, :scenario_id],
:unique => true,
:name => 'index_ad_on_updated_at_and_planning_element_id'
end
end

@ -54,27 +54,6 @@ Given /^there are the following project types:$/ do |table|
end
end
Given /^there is a scenario "([^"]*)" in project "([^"]*)"$/ do |scenario_name, project_name|
FactoryGirl.create(:scenario, :name => scenario_name, :project_id => Project.find_by_name!(project_name).id)
end
Given /^there are the following alternate dates for "([^"]*)":$/ do |scenario_name, table|
scenario = Scenario.find_by_name!(scenario_name)
table.map_headers! { |header| header.underscore.gsub(' ', '_') }
table.hashes.each do |row|
planning_element = PlanningElement.find_by_subject!(row["planning_element_subject"])
planning_element.scenarios = {scenario.id.to_s => {"id" => scenario.id.to_s, "start_date" => row["start_date"], "due_date" => row["due_date"]} }
planning_element.save!
end
end
Given /^I delete the scenario "([^"]*)"$/ do |scenario_name|
scenario = Scenario.find_by_name!(scenario_name)
scenario.destroy
end
Given /^there are the following projects of type "([^"]*)":$/ do |project_type_name, table|
table.raw.flatten.each do |name|
step %Q{there is a project named "#{name}" of type "#{project_type_name}"}

@ -1,27 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class OpenProject::JournalFormatter::ScenarioDate < JournalFormatter::Datetime
unloadable
private
def label(key)
key_match = /^scenario_(\d+)_(start|due)_date$/.match(key)
scenario = Scenario.find_by_id(key_match[1])
scenario_name = scenario ? scenario.name : l(:label_scenario_deleted)
l(:"label_scenario_#{key_match[2]}_date", :title => scenario_name)
end
end

@ -55,7 +55,6 @@ Redmine::AccessControl.map do |map|
{
:types => [:index, :show],
:projects => [:show],
:scenarios => [:index, :show],
:projects => [:show],
:activities => [:index]
},
@ -64,8 +63,7 @@ Redmine::AccessControl.map do |map|
map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
map.permission :edit_project,
{
:projects => [:settings, :edit, :update],
:scenarios => [:new, :create, :edit, :update, :confirm_destroy, :destroy]
:projects => [:settings, :edit, :update]
},
:require => :member
map.permission :select_project_modules, {:projects => :modules}, :require => :member

@ -1,171 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../../../spec_helper', __FILE__)
describe Api::V2::ScenariosController do
describe 'index.xml' do
describe 'w/o a given project' do
it 'renders a 404 Not Found page' do
get 'index', :format => 'xml'
response.response_code.should == 404
end
end
describe 'w/ an unknown project' do
it 'renders a 404 Not Found page' do
get 'index', :project_id => '4711', :format => 'xml'
response.response_code.should == 404
end
end
describe 'w/ a known project' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project') }
describe 'w/o any scenarios within the project' do
it 'assigns an empty scenarios array' do
get 'index', :project_id => project.id, :format => 'xml'
assigns(:scenarios).should == []
end
it 'renders the index builder template' do
get 'index', :project_id => project.id, :format => 'xml'
response.should render_template('scenarios/index', :formats => ["api"])
end
end
describe 'w/ 3 scenarios within the project' do
before do
@created_scenarios = [
FactoryGirl.create(:scenario, :project_id => project.id),
FactoryGirl.create(:scenario, :project_id => project.id),
FactoryGirl.create(:scenario, :project_id => project.id)
]
end
it 'assigns a scenarios array containing all three elements' do
get 'index', :project_id => project.id, :format => 'xml'
assigns(:scenarios).should =~ @created_scenarios
end
it 'renders the index builder template' do
get 'index', :project_id => project.id, :format => 'xml'
response.should render_template('scenarios/index', :formats => ["api"])
end
end
end
describe 'access control' do
describe 'with a private project' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project',
:is_public => false) }
def fetch
get 'index', :project_id => project.id, :format => 'xml'
end
it_should_behave_like "a controller action which needs project permissions"
end
describe 'with a public project' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project',
:is_public => true) }
def fetch
get 'index', :project_id => project.id, :format => 'xml'
end
it_should_behave_like "a controller action with unrestricted access"
end
end
end
describe 'show.xml' do
describe 'w/o a valid scenario id' do
describe 'w/o a given project' do
it 'renders a 404 Not Found page' do
get 'show', :id => '4711', :format => 'xml'
response.response_code.should == 404
end
end
describe 'w/ an unknown project' do
it 'renders a 404 Not Found page' do
get 'index', :project_id => '4711', :id => '1337', :format => 'xml'
response.response_code.should == 404
end
end
describe 'w/ a known project' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project') }
it 'raises ActiveRecord::RecordNotFound errors' do
lambda do
get 'show', :project_id => project.id, :id => '1337', :format => 'xml'
end.should raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe 'w/ a valid scenario id' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project') }
let(:scenario) { FactoryGirl.create(:scenario, :project_id => project.id) }
describe 'w/o a given project' do
it 'renders a 404 Not Found page' do
get 'show', :id => scenario.id, :format => 'xml'
response.response_code.should == 404
end
end
describe 'w/ a known project' do
it 'assigns the scenario' do
get 'show', :project_id => project.id, :id => scenario.id, :format => 'xml'
assigns(:scenario).should == scenario
end
it 'renders the index builder template' do
get 'index', :project_id => project.id, :id => scenario.id, :format => 'xml'
response.should render_template('scenarios/index', :formats => ["api"])
end
end
describe 'access control' do
describe 'with a private project' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project',
:is_public => false) }
def fetch
get 'index', :project_id => project.id, :id => scenario.id, :format => 'xml'
end
it_should_behave_like "a controller action which needs project permissions"
end
describe 'with a public project' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project',
:is_public => true) }
def fetch
get 'index', :project_id => project.id, :id => scenario.id, :format => 'xml'
end
it_should_behave_like "a controller action with unrestricted access"
end
end
end
end
def project_settings_path(project)
{:controller => 'projects', :action => 'settings', :tab => 'timelines', :id => project}
end
end

@ -1,94 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
describe ScenariosController do
describe 'new.html' do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
def fetch
get 'new', :project_id => project.id
end
let(:permission) { :edit_project }
it_should_behave_like "a controller action which needs project permissions"
end
describe 'create.html' do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
def fetch
post 'create', :project_id => project.id,
:scenario => FactoryGirl.build(:scenario,
:project_id => project.id).attributes
end
let(:permission) { :edit_project }
def expect_redirect_to
project_settings_path(project)
end
it_should_behave_like "a controller action which needs project permissions"
end
describe 'edit.html' do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
let(:scenario) { FactoryGirl.create(:scenario, :project_id => project.id) }
def fetch
get 'edit', :project_id => project.id, :id => scenario.id
end
let(:permission) { :edit_project }
it_should_behave_like "a controller action which needs project permissions"
end
describe 'update.html' do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
let(:scenario) { FactoryGirl.create(:scenario, :project_id => project.id) }
def fetch
post 'update', :project_id => project.id, :id => scenario.id, :scenario => { "name" => "blubs" }
end
let(:permission) { :edit_project }
def expect_redirect_to
project_settings_path(project)
end
it_should_behave_like "a controller action which needs project permissions"
end
describe 'confirm_destroy.html' do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
let(:scenario) { FactoryGirl.create(:scenario, :project_id => project.id) }
def fetch
get 'confirm_destroy', :project_id => project.id, :id => scenario.id
end
let(:permission) { :edit_project }
it_should_behave_like "a controller action which needs project permissions"
end
describe 'destroy.html' do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
let(:scenario) { FactoryGirl.create(:scenario, :project_id => project.id) }
def fetch
post 'destroy', :project_id => project.id, :id => scenario.id
end
let(:permission) { :edit_project }
def expect_redirect_to
project_settings_path(project)
end
it_should_behave_like "a controller action which needs project permissions"
end
def project_settings_path(project)
{:controller => 'projects', :action => 'settings', :tab => 'timelines', :id => project}
end
end

@ -1,34 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
FactoryGirl.define do
factory(:alternate_date, :class => AlternateDate) do
sequence(:start_date) { |n| ((n - 1) * 7).days.since.to_date }
sequence(:due_date) { |n| (n * 7).days.since.to_date }
planning_element { |e| e.association(:planning_element) }
end
end
FactoryGirl.define do
factory(:alternate_scenaric_date, :parent => :alternate_date) do |d|
scenario { |e| e.association(:scenario) }
end
end
FactoryGirl.define do
factory(:alternate_historic_date, :parent => :alternate_date) do |d|
scenario nil
sequence(:created_at) { |n| n.weeks.ago.to_date }
sequence(:updated_at) { |n| n.weeks.ago.to_date }
end
end

@ -1,19 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
FactoryGirl.define do
factory(:scenario, :class => Scenario) do
sequence(:name) { |n| "Scenario No. #{n}" }
sequence(:description) { |n| "Scenario No. #{n} would allow us to launch last week." }
association :project
end
end

@ -50,7 +50,7 @@ FactoryGirl.define do
pe.due_date = pe.due_date + delay
pe.save
# predate all journals and alternate dates by one week.
# predate all journals by one week.
predate = Proc.new do |e|
fake = e.created_at - 1.week
@ -61,13 +61,10 @@ FactoryGirl.define do
end
PlanningElement.record_timestamps = false
AlternateDate.record_timestamps = false
Journal.record_timestamps = false
dates = pe.journals.map &predate
pe.alternate_dates.each &predate
AlternateDate.record_timestamps = true
Journal.record_timestamps = true
earliest_update = dates.min

@ -1,98 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper.rb")
describe OpenProject::JournalFormatter::ScenarioDate do
include ActionView::Helpers::TagHelper
# WARNING: the order of the modules is important to ensure that url_for of
# ActionController::UrlWriter is called and not the one of ActionView::Helpers::UrlHelper
include ActionView::Helpers::UrlHelper
include Rails.application.routes.url_helpers
include Redmine::I18n
struct = Struct.new("TimelinesScenarioDateTestJournal", :id, :journaled)
let(:klass) { OpenProject::JournalFormatter::ScenarioDate }
let(:journal_id) { 1 }
let(:scenario) { FactoryGirl.create(:scenario) }
let(:journal) do
Struct::TimelinesScenarioDateTestJournal.new(journal_id, scenario)
end
let(:instance) { klass.new(journal) }
let(:key) { "scenario_#{scenario.id}_#{date_type}_date" }
let(:date_type) { "start" }
describe :render do
describe "WITH rendering the start date
WITH the first value beeing nil, and the second a date
WITH the scenario existing" do
let(:new_date) { Date.today }
let(:expected) { I18n.t(:text_journal_set_to,
:label => "<strong>#{ I18n.t(:"label_scenario_#{date_type}_date", :title => scenario.name) }</strong>",
:value => "<i>#{format_date(new_date)}</i>") }
it { instance.render(key, [nil, new_date]).should == expected }
end
describe "WITH rendering the due date
WITH the first value beeing nil, and the second a date
WITH the scenario existing" do
let(:new_date) { Date.today }
let(:date_type) { "due" }
let(:expected) { I18n.t(:text_journal_set_to,
:label => "<strong>#{ I18n.t(:"label_scenario_#{date_type}_date", :title => scenario.name) }</strong>",
:value => "<i>#{format_date(new_date)}</i>") }
it { instance.render(key, [nil, new_date]).should == expected }
end
describe "WITH rendering the start date
WITH the first value beeing a date, and the second a date
WITH the scenario existing" do
let(:old_date) { Date.today - 4.days }
let(:new_date) { Date.today }
let(:expected) { I18n.t(:text_journal_changed,
:label => "<strong>#{ I18n.t(:"label_scenario_#{date_type}_date", :title => scenario.name) }</strong>",
:new => "<i>#{format_date(new_date)}</i>",
:old => "<i>#{format_date(old_date)}</i>") }
it { instance.render(key, [old_date, new_date]).should == expected }
end
describe "WITH rendering the start date
WITH the first value beeing a date, and the second nil
WITH the scenario existing" do
let(:old_date) { Date.today - 4.days }
let(:expected) { I18n.t(:text_journal_deleted,
:label => "<strong>#{ I18n.t(:"label_scenario_#{date_type}_date", :title => scenario.name) }</strong>",
:old => "<strike><i>#{format_date(old_date)}</i></strike>") }
it { instance.render(key, [old_date, nil]).should == expected }
end
describe "WITH rendering the start date
WITH the first value beeing nil, and the second a date
WITH the scenario is deleted" do
let(:new_date) { Date.today }
let(:key) { "scenario_0_#{date_type}_date" }
let(:expected) { I18n.t(:text_journal_set_to,
:label => "<strong>#{ I18n.t(:"label_scenario_#{date_type}_date",
:title => I18n.t(:label_scenario_deleted)) }</strong>",
:value => "<i>#{format_date(new_date)}</i>") }
it { instance.render(key, [nil, new_date]).should == expected }
end
end
end

@ -1,156 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
describe AlternateDate do
describe '- Relations ' do
describe '#project' do
it 'can read the planning_element w/ the help of the belongs_to association' do
planning_element = FactoryGirl.create(:planning_element)
alternate_date = FactoryGirl.create(:alternate_date,
:planning_element_id => planning_element.id)
alternate_date.reload
alternate_date.planning_element.should == planning_element
end
it 'can read the scenario w/ the help of the belongs_to association' do
scenario = FactoryGirl.create(:scenario)
alternate_date = FactoryGirl.create(:alternate_date,
:scenario_id => scenario.id)
alternate_date.reload
alternate_date.scenario.should == scenario
end
end
end
describe '- Scopes ' do
describe '.historic' do
it 'contains only elements not belonging to a scenario' do
planning_element = FactoryGirl.create(:planning_element)
# created automatically when creating the planning element
alternate_date1 = planning_element.alternate_dates.first
scenario = FactoryGirl.create(:scenario)
alternate_date2 = FactoryGirl.create(:alternate_date,
:scenario_id => scenario.id,
:planning_element_id => planning_element.id)
AlternateDate.historic.size.should == 1
AlternateDate.historic.should be_include(alternate_date1)
AlternateDate.historic.should_not be_include(alternate_date2)
end
end
describe '.scenaric' do
it 'contains only elements belonging to a scenario' do
planning_element = FactoryGirl.create(:planning_element)
# created automatically when creating the planning element
alternate_date1 = planning_element.alternate_dates.first
scenario = FactoryGirl.create(:scenario)
alternate_date2 = FactoryGirl.create(:alternate_date,
:scenario_id => scenario.id)
AlternateDate.scenaric.size.should == 1
AlternateDate.scenaric.should_not be_include(alternate_date1)
AlternateDate.scenaric.should be_include(alternate_date2)
end
end
end
describe '- Validations ' do
let(:attributes) {
{:planning_element_id => 1,
:start_date => Date.today,
:due_date => Date.today + 2.weeks}
}
before {
FactoryGirl.create(:planning_element, :id => 1)
ApplicationHelper.set_language_if_valid 'en'
}
it { AlternateDate.new.tap { |ad| ad.send(:assign_attributes, attributes, :without_protection => true) }.should be_valid }
describe 'start_date' do
it 'is invalid w/o a start_date' do
attributes[:start_date] = nil
alternate_date = AlternateDate.new.tap { |ad| ad.send(:assign_attributes, attributes, :without_protection => true) }
alternate_date.should_not be_valid
alternate_date.errors[:start_date].should be_present
alternate_date.errors[:start_date].should == ["can't be blank"]
end
end
describe 'due_date' do
it 'is invalid w/o a due_date' do
attributes[:due_date] = nil
alternate_date = AlternateDate.new.tap { |ad| ad.send(:assign_attributes, attributes, :without_protection => true) }
alternate_date.should_not be_valid
alternate_date.errors[:due_date].should be_present
alternate_date.errors[:due_date].should == ["can't be blank"]
end
it 'is invalid if start_date is after due_date' do
attributes[:start_date] = Date.today
attributes[:due_date] = Date.today - 1.week
alternate_date = AlternateDate.new.tap { |ad| ad.send(:assign_attributes, attributes, :without_protection => true) }
alternate_date.should_not be_valid
alternate_date.errors[:due_date].should be_present
alternate_date.errors[:due_date].should == ["must be greater than start date"]
end
it 'is invalid if planning_element is milestone and due_date is not on start_date' do
type = FactoryGirl.build(:type, :is_milestone => true)
attributes[:start_date] = Date.today
attributes[:due_date] = Date.today + 1.week
attributes[:planning_element] = FactoryGirl.build(:planning_element, :type => type)
alternate_date = AlternateDate.new.tap { |ad| ad.send(:assign_attributes, attributes, :without_protection => true) }
alternate_date.should_not be_valid
alternate_date.errors[:due_date].should be_present
alternate_date.errors[:due_date].should == ["is not on start date, although this is required for milestones"]
end
end
describe 'planning_element' do
it 'is invalid w/o a planning_element' do
attributes[:planning_element_id] = nil
alternate_date = AlternateDate.new.tap { |ad| ad.send(:assign_attributes, attributes, :without_protection => true) }
alternate_date.should_not be_valid
alternate_date.errors[:planning_element].should be_present
alternate_date.errors[:planning_element].should == ["can't be blank"]
end
end
end
describe ' - Instance Methods' do
it_should_behave_like "a model with non-negative duration"
end
end

@ -142,24 +142,6 @@ describe PermittedParams do
end
end
describe :scenario do
it "should permit name" do
hash = { "name" => "blubs" }
params = ActionController::Parameters.new(:scenario => hash)
PermittedParams.new(params, user).scenario.should == hash
end
it "should permit description" do
hash = { "description" => "blubs" }
params = ActionController::Parameters.new(:scenario => hash)
PermittedParams.new(params, user).scenario.should == hash
end
end
describe :planning_element do
it "should permit planning_element" do
hash = { "subject" => "blubs" }
@ -193,14 +175,6 @@ describe PermittedParams do
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit scenarios" do
hash = { "scenarios" => {'id' => "1", 'start_date' => '2012-01-01', 'due_date' => '2012-01-03' } }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit note" do
hash = { "note" => "lorem" }

@ -1,62 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
describe PlanningElementScenario do
let(:project) { FactoryGirl.build(:project) }
let(:planning_element) { FactoryGirl.build(:planning_element, :project => project) }
let(:scenario) { FactoryGirl.build(:scenario, :project => project) }
let(:alternate_date) { FactoryGirl.build(:alternate_date, :scenario => scenario,
:planning_element => planning_element) }
let(:subject) { PlanningElementScenario.new(alternate_date) }
it 'delegates start_date to the alternate date' do
subject.start_date.should == alternate_date.start_date
end
it 'delegates start_date= to the alternate date' do
d = Date.new(1982, 01, 31)
subject.start_date = d
alternate_date.start_date.should == d
end
it 'delegates due_date to the alternate date' do
subject.due_date.should == alternate_date.due_date
end
it 'delegates due_date= to the alternate date' do
d = Date.new(1982, 01, 31)
subject.due_date = d
alternate_date.due_date.should == d
end
it 'delegates duration to the alternate date' do
subject.duration.should eql alternate_date.duration
end
it 'delegates scenario to the alternate date' do
subject.scenario.should == alternate_date.scenario
end
it 'delegates scenario_id to the alternate date' do
subject.scenario_id.should == alternate_date.scenario_id
end
it 'delegates name to the scenario' do
subject.name.should == scenario.name
end
it 'delegates id to the scenario' do
subject.id.should == scenario.id
end
end

@ -215,345 +215,6 @@ describe PlanningElement do
end
end
describe 'alternate dates' do
describe 'auto-creation' do
let(:attributes) {
{:subject => 'Planning Element No. 1',
:start_date => Date.today,
:due_date => Date.today + 2.weeks,
:project_id => project.id}
}
it 'creates an alternate date whenever a planning element is created' do
pe = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
pe.save
pe.reload
pe.alternate_dates.should_not be_empty
pe.alternate_dates.size.should == 1
ad = pe.alternate_dates.last
ad.start_date.should == pe.start_date
ad.due_date.should == pe.due_date
end
it 'creates an alternate date whenever start or end date is updated' do
pe = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
pe.save
pe.reload
pe.update_attributes(:start_date => Date.today + 1.day,
:due_date => Date.today + 1.day + 2.weeks)
pe.alternate_dates.should_not be_empty
pe.alternate_dates.size.should == 2
ad = pe.alternate_dates.last
ad.start_date.should == pe.start_date
ad.due_date.should == pe.due_date
end
it 'does not create an alternate date when other attributes are changed' do
pe = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
pe.save
pe.reload
pe.update_attributes(:subject => 'Things')
pe.alternate_dates.size.should == 1
end
end
describe 'at_time scope' do
let(:changes) { [
{:time => Date.new(2011, 1, 1).to_time,
:start_date => Date.new(2012, 1, 1),
:due_date => Date.new(2012, 1, 2),
:due_date => Date.new(2012, 1, 2)},
{:time => Date.new(2011, 1, 2).to_time,
:start_date => Date.new(2012, 2, 1),
:due_date => Date.new(2012, 2, 2),
:due_date => Date.new(2012, 2, 2)},
{:time => Date.new(2011, 1, 3).to_time,
:start_date => Date.new(2011, 12, 1),
:due_date => Date.new(2012, 12, 2),
:due_date => Date.new(2011, 12, 2)}
] }
before do
# create proper planning element history and journals
initial = changes.first
pe = FactoryGirl.create(:planning_element,
:start_date => initial[:start_date],
:due_date => initial[:due_date],
:created_at => initial[:time],
:updated_at => initial[:time])
begin
PlanningElement.record_timestamps = false
# This will keep an api builder intact when calling partials
pe.reload
changes[1..-1].each do |change|
pe[:start_date] = change[:start_date]
pe[:due_date] = change[:due_date]
pe[:updated_at] = change[:time]
pe.save
pe.reload
end
ensure
PlanningElement.record_timestamps = true
end
# Kill auto-generated alterate dates. We'll create our own
pe.alternate_dates.delete_all
changes.each do |change|
FactoryGirl.create(:alternate_date,
:planning_element_id => pe.id,
:start_date => change[:start_date],
:due_date => change[:due_date],
:created_at => change[:time],
:updated_at => change[:time])
end
pe.alternate_dates.reload
@pe = pe
end
it 'returns the current data when timestamp is after last edit' do
fetched = PlanningElement.at_time(changes.last[:time] + 1.day).find(@pe.id)
fetched.start_date.should == @pe.start_date
fetched.due_date.should == @pe.due_date
fetched.updated_at == @pe.updated_at
fetched.should be_readonly
end
it 'returns nothing when timestamp is before first edit' do
expect {
PlanningElement.at_time(changes.first[:time] - 1.day).find(@pe.id)
}.to raise_error(ActiveRecord::RecordNotFound)
end
it 'returns intial data when timestamp is between first and second edit' do
edit = changes.first
fetched = PlanningElement.at_time(edit[:time] + 1.hour).find(@pe.id)
fetched.start_date.should == edit[:start_date]
fetched.due_date.should == edit[:due_date]
fetched.updated_at == edit[:updated_at]
fetched.should be_readonly
end
it 'returns intermediate data when timestamp is between second and last edit' do
edit = changes.second
fetched = PlanningElement.at_time(edit[:time] + 1.hour).find(@pe.id)
fetched.start_date.should == edit[:start_date]
fetched.due_date.should == edit[:due_date]
fetched.updated_at == edit[:updated_at]
fetched.should be_readonly
end
end
end
describe 'scenarios' do
let(:project) { FactoryGirl.create(:project) }
let(:planning_element) { FactoryGirl.create(:planning_element,
:project_id => project.id) }
describe 'w/o scenarios' do
describe '#scenarios' do
it 'returns an empty array' do
planning_element.scenarios.should be_empty
planning_element.scenarios.should == []
end
end
describe '#all_scenarios' do
it 'returns an empty array' do
planning_element.all_scenarios.should be_empty
planning_element.all_scenarios.should == []
end
end
end
describe 'w/ scenarios' do
let(:scenario_a) {FactoryGirl.create(:scenario,
:project_id => project.id) }
let(:scenario_b) {FactoryGirl.create(:scenario,
:project_id => project.id) }
before { scenario_a; scenario_b }
describe "w/o alternate dates" do
describe '#scenarios' do
it 'returns an empty array' do
planning_element.scenarios.should be_empty
planning_element.scenarios.should == []
end
end
describe '#all_scenarios' do
it 'returns an with all scenario w/o dates' do
scenarios = planning_element.all_scenarios
scenarios.size.should == 2
first = scenarios.first
first.scenario.should == scenario_a
first.planning_element.should == planning_element
first.start_date.should be_nil
first.due_date.should be_nil
second = scenarios.second
second.scenario.should == scenario_b
second.planning_element.should == planning_element
second.start_date.should be_nil
second.due_date.should be_nil
end
end
end
describe 'w/ alternate dates' do
let(:alternate_date) { FactoryGirl.create(:alternate_date,
:planning_element_id => planning_element.id,
:scenario_id => scenario_a.id) }
before { alternate_date; planning_element.reload }
describe '#scenarios' do
it 'returns an array w/ one element' do
scenarios = planning_element.scenarios
scenarios.size.should == 1
first = scenarios.first
first.scenario.should == scenario_a
first.planning_element.should == planning_element
first.start_date.should == alternate_date.start_date
first.due_date.should == alternate_date.due_date
end
end
describe '#all_scenarios' do
it 'returns an array' do
scenarios = planning_element.all_scenarios
scenarios.size.should == 2
first = scenarios.first
first.scenario.should == scenario_a
first.planning_element.should == planning_element
first.start_date.should == alternate_date.start_date
first.due_date.should == alternate_date.due_date
second = scenarios.second
second.scenario.should == scenario_b
second.planning_element.should == planning_element
second.start_date.should be_nil
second.due_date.should be_nil
end
end
end
end
end
describe 'scenarios=' do
let(:project) { FactoryGirl.create(:project) }
let(:planning_element) { FactoryGirl.create(:planning_element,
:project_id => project.id) }
let(:scenario) { FactoryGirl.create(:scenario,
:project_id => project.id) }
it 'creates alternate dates if there are none' do
planning_element; scenario
planning_element.reload
planning_element.alternate_dates.scenaric.should be_empty
planning_element.scenarios = [
{'id' => scenario.id, 'start_date' => Date.new(2012, 1, 2), 'due_date' => Date.new(2012, 1, 3) }
]
planning_element.save!
planning_element.reload
planning_element.alternate_dates.scenaric.should_not be_empty
planning_element.alternate_dates.scenaric.size.should == 1
alternate_date = planning_element.alternate_dates.scenaric.first
alternate_date.scenario.should == scenario
alternate_date.start_date.should == Date.new(2012, 1, 2)
alternate_date.due_date.should == Date.new(2012, 1, 3)
alternate_date.planning_element.should == planning_element
end
it 'updates alternate dates if there are some' do
alternate_date = FactoryGirl.create(:alternate_date,
:planning_element_id => planning_element.id,
:scenario_id => scenario.id,
:start_date => Date.new(2012, 1, 2),
:due_date => Date.new(2012, 1, 3))
planning_element.reload
planning_element.alternate_dates.scenaric.size.should == 1
planning_element.alternate_dates.scenaric.first.should == alternate_date
planning_element.scenarios = [
{'id' => scenario.id, 'start_date' => Date.new(2012, 2, 2), 'due_date' => Date.new(2012, 2, 3) }
]
planning_element.save!
planning_element.reload
planning_element.alternate_dates.scenaric.should_not be_empty
planning_element.alternate_dates.scenaric.size.should == 1
alternate_date.reload
planning_element.alternate_dates.scenaric.first.should == alternate_date
alternate_date.scenario.should == scenario
alternate_date.start_date.should == Date.new(2012, 2, 2)
alternate_date.due_date.should == Date.new(2012, 2, 3)
alternate_date.planning_element.should == planning_element
end
it 'deletes alternate_dates, if they are marked for destruction' do
alternate_date = FactoryGirl.create(:alternate_date,
:planning_element_id => planning_element.id,
:scenario_id => scenario.id,
:start_date => Date.new(2012, 1, 2),
:due_date => Date.new(2012, 1, 3))
planning_element.reload
planning_element.alternate_dates.scenaric.size.should == 1
planning_element.alternate_dates.scenaric.first.should == alternate_date
planning_element.scenarios = [
{'id' => scenario.id,
'start_date' => Date.new(2012, 2, 2),
'due_date' => Date.new(2012, 2, 3),
'_destroy' => '1'}
]
planning_element.save!
planning_element.reload
planning_element.alternate_dates.scenaric.should be_empty
planning_element.alternate_dates.scenaric.size.should == 0
expect { alternate_date.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe 'journal' do
let(:responsible) { FactoryGirl.create(:user) }
let(:type) { FactoryGirl.create(:type) }
@ -644,84 +305,6 @@ describe PlanningElement do
end
end
describe "a planning elements' journals includes changes to associated alternate dates start/due date" do
let(:journal_planning_element) { FactoryGirl.create(:planning_element) }
let(:journal_scenario) { FactoryGirl.create(:scenario) }
# PlanningElements create a alternate_date on save by default, so just use that
it "should create a journal entry if a scenario gets assigned to an alternate date" do
pending "Fails because of normalized AAJ implementation"
alternate_date = journal_planning_element.alternate_dates(true).first.tap do |ad|
ad.scenario = journal_scenario
end
journal_planning_element.save!
journal_planning_element.journals.size.should == 2
journal_planning_element.journals[0].changed_data.keys.select {|k| !!(/^scenario_(\d*)_(start|due)_date$/.match k.to_s) }.should be_empty
(date_keys = journal_planning_element.journals[1].changed_data.keys.select do |k|
!!(/^scenario_(\d*)_(start|due)_date$/.match k.to_s)
end).should_not be_empty
date_keys.size.should == 2
date_keys.each do |k|
journal_planning_element.journals[1].changed_data[k][0].should == nil
journal_planning_element.journals[1].changed_data[k][1].acts_like_date?.should be_true
end
end
it "should create a journal entry if a scenario gets removed from an alternate date" do
pending "Fails because of normalized AAJ implementation"
# PlanningElements create a alternate_date on save by default, so just use that
alternate_date = journal_planning_element.alternate_dates(true).first.tap do |ad|
ad.scenario = journal_scenario
end
journal_planning_element.save!
alternate_date.mark_for_destruction
journal_planning_element.save!
(date_keys = journal_planning_element.journals[2].changed_data.keys.select do |k|
!!(/^scenario_(\d*)_(start|due)_date$/.match k.to_s)
end)
date_keys.size.should == 2
date_keys.each do |k|
journal_planning_element.journals[2].changed_data[k][0].acts_like_date?.should be_true
journal_planning_element.journals[2].changed_data[k][1].should == nil
end
end
it "should create seperate journal entries for start_date and due_date if only one of 'em is modified" do
pending "Fails because of normalized AAJ implementation"
# PlanningElements create an alternate_date on save by default, so just use that
alternate_date = journal_planning_element.alternate_dates(true).first.tap do |ad|
ad.start_date = Date.today
ad.due_date = Date.today + 1.month
ad.scenario = journal_scenario
end
journal_planning_element.save!
old_start = alternate_date.start_date
old_end = alternate_date.due_date
alternate_date.start_date = new_start = alternate_date.start_date + 1.day
journal_planning_element.save!
(date_keys = (start_date_journal = journal_planning_element.journals.last).changed_data.keys.select do |k|
!!(/^scenario_(\d*)_(start|due)_date$/.match k.to_s)
end)
date_keys.size.should == 1
start_date_journal.changed_data[date_keys.first][0].acts_like_date?.should be_true
start_date_journal.changed_data[date_keys.first][0].should == old_start
start_date_journal.changed_data[date_keys.first][1].acts_like_date?.should be_true
start_date_journal.changed_data[date_keys.first][1].should == new_start
end
end
end
describe 'acts as paranoid trash' do

@ -1,97 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
describe Scenario do
describe '- Relations ' do
describe '#project' do
it 'can read the project w/ the help of the belongs_to association' do
project = FactoryGirl.create(:project)
scenario = FactoryGirl.create(:scenario,
:project_id => project.id)
scenario.reload
scenario.project.should == project
end
end
describe '#alternate_dates' do
it 'can read alternate_dates w/ the help of the has_many association' do
scenario = FactoryGirl.create(:scenario)
alternate_date = FactoryGirl.create(:alternate_date,
:scenario_id => scenario.id)
scenario.reload
scenario.alternate_dates.size.should == 1
scenario.alternate_dates.first.should == alternate_date
end
it 'deletes associated alternate_dates' do
scenario = FactoryGirl.create(:scenario)
alternate_date = FactoryGirl.create(:alternate_date,
:scenario_id => scenario.id)
scenario.reload
scenario.destroy
expect { alternate_date.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
describe '- Validations ' do
let(:attributes) {
{:name => 'Scenario No. 1',
:project_id => 1}
}
before { FactoryGirl.create(:project, :id => 1) }
it { Scenario.new.tap { |s| s.send(:assign_attributes, attributes, :without_protection => true) }.should be_valid }
describe 'name' do
it 'is invalid w/o a name' do
attributes[:name] = nil
scenario = Scenario.new.tap { |s| s.send(:assign_attributes, attributes, :without_protection => true) }
scenario.should_not be_valid
scenario.errors[:name].should be_present
scenario.errors[:name].should == ["can't be blank"]
end
it 'is invalid w/ a name longer than 255 characters' do
attributes[:name] = "A" * 500
scenario = Scenario.new.tap { |s| s.send(:assign_attributes, attributes, :without_protection => true) }
scenario.should_not be_valid
scenario.errors[:name].should be_present
scenario.errors[:name].should == ["is too long (maximum is 255 characters)"]
end
end
describe 'project' do
it 'is invalid w/o a project' do
attributes[:project_id] = nil
scenario = Scenario.new.tap { |s| s.send(:assign_attributes, attributes, :without_protection => true) }
scenario.should_not be_valid
scenario.errors[:project].should be_present
scenario.errors[:project].should == ["can't be blank"]
end
end
end
end

@ -104,8 +104,6 @@ describe Project do
expect { planning_element.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'destroys associated planning elements so that alternate dates may also be deleted'
end
describe '#reportings' do
@ -155,31 +153,6 @@ describe Project do
end
end
describe '#scenarios' do
it 'can read reportings w/ the help of the has_many association' do
project = FactoryGirl.create(:project)
scenario = FactoryGirl.create(:scenario, :project_id => project.id)
project.reload
project.scenarios.size.should == 1
project.scenarios.first.should == scenario
end
it 'deletes associated scenarios' do
project = FactoryGirl.create(:project)
scenario = FactoryGirl.create(:scenario, :project_id => project.id)
project.reload
project.destroy
expect { scenario.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'destroys associated scenarios so that alternate dates may also be deleted'
end
describe ' - project associations ' do
describe '#project_associations' do
# has_many

@ -1,41 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
shared_examples_for "a model with non-negative duration" do
# it is assumed, that planning elements start on start_date 00:01 and end
# on due_date 23:59. Therefore, if start_date and due_date are on the very
# same day, the duration should be 1.
describe 'duration' do
describe 'when start date == end date' do
it 'is 1' do
subject.start_date = Date.today
subject.due_date = Date.today
subject.duration.should == 1
end
end
describe 'when end date > start date' do
it 'is the difference between end date and start date plus one day' do
subject.start_date = Date.today - 5.days
subject.due_date = Date.today
subject.duration.should == 6
end
end
describe 'when start date > end date' do
it 'is 1' do
subject.start_date = Date.today
subject.due_date = Date.today - 5.days
subject.duration.should == 1
end
end
end
end

@ -24,7 +24,6 @@ describe 'api/v2/planning_elements/_planning_element.api' do
before :each do
view.stub(:include_journals?).and_return(false)
view.stub(:include_scenarios?).and_return(true)
end
describe 'with an assigned planning element' do
@ -205,103 +204,6 @@ describe 'api/v2/planning_elements/_planning_element.api' do
end
end
describe 'with scenaric alternate dates' do
let(:project) { FactoryGirl.create(:project) }
let(:scenario_a) { FactoryGirl.create(:scenario,
:project_id => project.id,
:id => 2001,
:name => 'Scenario A') }
let(:scenario_b) { FactoryGirl.create(:scenario,
:project_id => project.id,
:id => 2010,
:name => 'Scenario B') }
let(:scenario_c) { FactoryGirl.create(:scenario,
:project_id => project.id,
:id => 2030,
:name => 'Scenario C') }
let(:scenario_d) { FactoryGirl.create(:scenario,
:project_id => project.id,
:id => 2100,
:name => 'Scenario D') }
let(:planning_element) { FactoryGirl.create(:planning_element,
:project_id => project.id) }
before do
@scenario_dates = [
FactoryGirl.create(:alternate_scenaric_date,
:planning_element_id => planning_element.id,
:scenario_id => scenario_a.id,
:start_date => Date.new(2011, 01, 01),
:due_date => Date.new(2011, 01, 31)),
FactoryGirl.create(:alternate_scenaric_date,
:planning_element_id => planning_element.id,
:scenario_id => scenario_b.id,
:start_date => Date.new(2011, 02, 01),
:due_date => Date.new(2011, 02, 28)),
FactoryGirl.create(:alternate_scenaric_date,
:planning_element_id => planning_element.id,
:scenario_id => scenario_c.id,
:start_date => Date.new(2011, 03, 01),
:due_date => Date.new(2011, 03, 31))
]
# This is just created to make sure, that it is ok, that there are
# scenarios in the project for which a planning element may have no
# alternate dates.
scenario_d
end
describe 'planning_element node' do
it 'contains a scenarios node of type array and a size' do
render
response.should have_selector('planning_element scenarios[type=array][size="3"]')
end
describe 'scenarios node' do
it 'contains a scenario node for every available alternate scenario' do
render
response.should have_selector("planning_element scenarios scenario", :count => 3)
end
it 'scenarios nodes have an id and name attribute' do
render
response.should have_selector("planning_element scenarios") do
with_tag("scenario[id=2001][name=Scenario A]")
with_tag("scenario[id=2010][name=Scenario B]")
with_tag("scenario[id=2030][name=Scenario C]")
end
end
it 'scenario nodes contain start_date and due_date nodes containing the alternate dates' do
render
response.should have_selector('planning_element scenarios scenario[name="Scenario A"]') do
with_tag("start_date", :text => '2011-01-01')
with_tag("end_date", :text => '2011-01-31')
end
response.should have_selector('planning_element scenarios scenario[name="Scenario B"]') do
with_tag("start_date", :text => '2011-02-01')
with_tag("end_date", :text => '2011-02-28')
end
response.should have_selector('planning_element scenarios scenario[name="Scenario C"]') do
with_tag("start_date", :text => '2011-03-01')
with_tag("end_date", :text => '2011-03-31')
end
end
end
end
end
describe "a destroyed planning element" do
let(:planning_element) { FactoryGirl.create(:planning_element) }
before do

@ -19,7 +19,6 @@ describe 'api/v2/planning_elements/destroy.api.rsb' do
before do
view.stub(:include_journals?).and_return(false)
view.stub(:include_scenarios?).and_return(false)
params[:format] = 'xml'
end

@ -19,7 +19,7 @@ describe 'api/v2/planning_elements/index.api.rsb' do
before do
view.stub(:include_journals?).and_return(false)
view.stub(:include_scenarios?).and_return(false)
params[:format] = 'xml'
end

@ -19,7 +19,7 @@ describe 'api/v2/planning_elements/show.api.rsb' do
before do
view.stub(:include_journals?).and_return(false)
view.stub(:include_scenarios?).and_return(false)
params[:format] = 'xml'
end

@ -74,12 +74,6 @@ describe 'api/v2/projects/_project.api' do
response.should_not have_selector('project planning_element_types')
end
it 'does not contain a scenarios element' do
render
response.should_not have_selector('project scenarios')
end
it 'does not contain a project_associations element' do
render
@ -229,18 +223,17 @@ describe 'api/v2/projects/_project.api' do
let(:project) { FactoryGirl.create(:project) }
before do
@project_scenarios = [
FactoryGirl.create(:project_association,
:project_a_id => project.id,
:project_b_id => FactoryGirl.create(:project, :is_public => true).id),
FactoryGirl.create(:project_association,
:project_b_id => project.id,
:project_a_id => FactoryGirl.create(:project, :is_public => true).id)
]
FactoryGirl.create(:project_association,
:project_a_id => project.id,
:project_b_id => FactoryGirl.create(:project, :is_public => true).id)
FactoryGirl.create(:project_association,
:project_b_id => project.id,
:project_a_id => FactoryGirl.create(:project, :is_public => true).id)
# Adding invisible association to make sure, that it is not included in the output
FactoryGirl.create(:project_association,
:project_a_id => project.id,
:project_b_id => FactoryGirl.create(:project, :is_public => false).id)
:project_a_id => project.id,
:project_b_id => FactoryGirl.create(:project, :is_public => false).id)
end
describe 'project node' do
@ -259,32 +252,4 @@ describe 'api/v2/projects/_project.api' do
end
end
end
describe 'with a project having 3 scenarios' do
let(:project) { FactoryGirl.create(:project) }
before do
@project_scenarios = [
FactoryGirl.create(:scenario, :project_id => project.id),
FactoryGirl.create(:scenario, :project_id => project.id),
FactoryGirl.create(:scenario, :project_id => project.id)
]
end
describe 'project node' do
it 'contains a scenarios element of type array with a size of 3' do
render
response.should have_selector('project scenarios[type=array][size="3"]', :count => 1)
end
describe 'scenarios node' do
it 'contains 3 scenario elements having id and name attributes' do
render
response.should have_selector('project scenarios scenario[id][name]', :count => 3)
end
end
end
end
end

@ -1,75 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../../../../spec_helper', __FILE__)
describe 'api/v2/scenarios/_scenario.api' do
before do
view.extend TimelinesHelper
end
# added to pass in locals
def render
params[:format] = 'xml'
super(:partial => 'api/v2/scenarios/scenario.api', :object => scenario)
end
describe 'with an assigned scenario' do
let(:project) { FactoryGirl.create(:project, :id => 4711,
:identifier => 'test_project',
:name => 'Test Project') }
let(:scenario) { FactoryGirl.build(:scenario,
:id => 1,
:project_id => project.id,
:name => 'Awesometastic scenario',
:description => 'Description of this scenario',
:created_at => Time.parse('Thu Jan 06 12:35:00 +0100 2011'),
:updated_at => Time.parse('Fri Jan 07 12:35:00 +0100 2011')) }
it 'renders a scenario node' do
render
response.should have_selector('scenario', :count => 1)
end
describe 'scenario node' do
it 'contains an id element containing the scenario id' do
render
response.should have_selector('scenario id', :text => '1')
end
it 'contains a project element containing the scenario\'s project id, identifier and name' do
render
response.should have_selector('scenario project[id="4711"][identifier="test_project"][name="Test Project"]')
end
it 'contains an name element containing the scenario name' do
render
response.should have_selector('scenario name', :text => 'Awesometastic scenario')
end
it 'contains an description element containing the scenario description' do
render
response.should have_selector('scenario description', :text => 'Description of this scenario')
end
it 'contains a created_at element containing the scenario created_at in UTC in ISO 8601' do
render
response.should have_selector('scenario created_at', :text => '2011-01-06T11:35:00Z')
end
it 'contains an updated_at element containing the scenario updated_at in UTC in ISO 8601' do
render
response.should have_selector('scenario updated_at', :text => '2011-01-07T11:35:00Z')
end
end
end
end

@ -1,86 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../../../../spec_helper', __FILE__)
describe 'api/v2/scenarios/index.api.rsb' do
before do
view.extend TimelinesHelper
end
before do
params[:format] = 'xml'
end
describe 'with no scenarios available' do
it 'renders an empty scenarios document' do
assign(:scenarios, [])
render
response.should have_selector('scenarios', :count => 1)
response.should have_selector('scenarios[type=array][size="0"]') do
without_tag 'scenario'
end
end
end
describe 'with 3 scenarios available' do
let(:scenarios) do
[
FactoryGirl.build(:scenario),
FactoryGirl.build(:scenario),
FactoryGirl.build(:scenario)
]
end
it 'renders a scenarios document with the size 3 of array' do
assign(:scenarios, scenarios)
render
response.should have_selector('scenarios', :count => 1)
response.should have_selector('scenarios[type=array][size="3"]')
end
it 'renders a scenario for each assigned scenario' do
assign(:scenarios, scenarios)
render
response.should have_selector('scenarios scenario', :count => 3)
end
it 'renders the _scenario template for each assigned scenario' do
assign(:scenarios, scenarios)
view.should_receive(:render).exactly(3).times.with(hash_including(:partial => '/api/v2/scenarios/scenario.api')).and_return('')
# just to call the speced template despite the should receive expectations above
view.should_receive(:render).once.with({ :template=>"api/v2/scenarios/index", :handlers=>["rsb"], :formats=>["api"] }, {}).and_call_original
render
end
it 'passes the scenarios as local var to the partial' do
assign(:scenarios, scenarios)
view.should_receive(:render).once.with(hash_including(:object => scenarios.first)).and_return('')
view.should_receive(:render).once.with(hash_including(:object => scenarios.second)).and_return('')
view.should_receive(:render).once.with(hash_including(:object => scenarios.third)).and_return('')
# just to call the speced template despite the should receive expectations above
view.should_receive(:render).once.with({ :template=>"api/v2/scenarios/index", :handlers=>["rsb"], :formats=>["api"] }, {}).and_call_original
render
end
end
end

@ -1,56 +0,0 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../../../../spec_helper', __FILE__)
describe 'api/v2/scenarios/show.api.rsb' do
before do
view.extend TimelinesHelper
end
before do
params[:format] = 'xml'
end
describe 'with an assigned scenario' do
let(:scenario) { FactoryGirl.build(:scenario) }
before do
assign(:scenario, scenario)
end
it 'renders a scenario document' do
render
response.should have_selector('scenario', :count => 1)
end
it 'renders the _scenario template once' do
view.should_receive(:render).once.with(hash_including(:partial => '/api/v2/scenarios/scenario.api')).and_return('')
# just to render the speced template despite the should receive expectations above
view.should_receive(:render).once.with({:template=>"api/v2/scenarios/show", :handlers=>["rsb"], :formats=>["api"]}, {}).and_call_original
render
end
it 'passes the scenario as local var to the partial' do
view.should_receive(:render).once.with(hash_including(:object => scenario)).and_return('')
# just to render the speced template despite the should receive expectations above
view.should_receive(:render).once.with({:template=>"api/v2/scenarios/show", :handlers=>["rsb"], :formats=>["api"]}, {}).and_call_original
render
end
end
end
Loading…
Cancel
Save