Merge remote-tracking branch 'opf/feature/rails3' into feature/remove-issues

Conflicts:
	app/models/work_package.rb
pull/421/head
Martin Czuchra 11 years ago
commit e570aee72d
  1. 18
      app/assets/javascripts/application.js
  2. 16
      app/assets/javascripts/breadcrumb.js
  3. 601
      app/assets/javascripts/modal.js
  4. 66
      app/assets/javascripts/timelines.js
  5. 43
      app/assets/javascripts/timelines_modal.js
  6. 4
      app/assets/stylesheets/application.css.erb
  7. 23
      app/assets/stylesheets/default/application.css.erb
  8. 4
      app/assets/stylesheets/jstoolbar.css.erb
  9. 20
      app/assets/stylesheets/select2_customizing.css.erb
  10. 13
      app/assets/stylesheets/timelines.css
  11. 4
      app/controllers/api/v2/planning_elements_controller.rb
  12. 6
      app/controllers/application_controller.rb
  13. 13
      app/controllers/my_controller.rb
  14. 2
      app/controllers/planning_element_journals_controller.rb
  15. 9
      app/controllers/repositories_controller.rb
  16. 42
      app/controllers/wiki_controller.rb
  17. 25
      app/controllers/work_packages/moves_controller.rb
  18. 4
      app/controllers/work_packages_controller.rb
  19. 2
      app/helpers/application_helper.rb
  20. 6
      app/helpers/planning_elements_helper.rb
  21. 26
      app/models/planning_element.rb
  22. 16
      app/models/project.rb
  23. 2
      app/models/repository/subversion.rb
  24. 3
      app/models/timeline.rb
  25. 3
      app/models/type.rb
  26. 23
      app/models/work_package.rb
  27. 67
      app/views/layouts/base.html.erb
  28. 6
      app/views/my/page.html.erb
  29. 6
      app/views/my/page_layout.html.erb
  30. 4
      app/views/timelines/_timeline.html.erb
  31. 36
      app/views/wiki/edit_parent_page.html.erb
  32. 11
      app/views/wiki/rename.html.erb
  33. 9
      app/views/wiki/show.html.erb
  34. 4
      app/views/work_packages/_subwork_packages_paragraph.html.erb
  35. 14
      config/locales/de.yml
  36. 21
      config/locales/en.yml
  37. 7
      config/routes.rb
  38. 72
      db/seeds/development.rb
  39. 10
      doc/CHANGELOG.md
  40. 17
      features/logout_ajax.feature
  41. 2
      features/planning_elements/show.feature
  42. 15
      features/step_definitions/breadcrumb_steps.rb
  43. 11
      features/step_definitions/general_steps.rb
  44. 34
      features/step_definitions/my_page_steps.rb
  45. 5
      features/step_definitions/planning_element_steps.rb
  46. 20
      features/step_definitions/timelines_then_steps.rb
  47. 27
      features/step_definitions/timelines_when_steps.rb
  48. 11
      features/step_definitions/web_steps.rb
  49. 11
      features/step_definitions/wiki_steps.rb
  50. 1
      features/step_definitions/work_package_steps.rb
  51. 5
      features/support/paths.rb
  52. 2
      features/timelines/show.feature
  53. 76
      features/timelines/timeline_modal_views.feature.disabled
  54. 18
      features/timelines/timeline_view.feature
  55. 18
      features/timelines/timeline_view_with_reporters.feature
  56. 12
      features/timelines/timeline_wiki_macro.feature
  57. 39
      features/wiki/parent_page.feature
  58. 2
      features/work_packages/changesets_on_show.feature
  59. 6
      features/work_packages/editable_fields.feature
  60. 2
      features/work_packages/error_on_update.feature
  61. 2
      features/work_packages/log_time_on_update.feature
  62. 4
      features/work_packages/moves/work_package_moves_new_copy.feature
  63. 2
      features/work_packages/navigate_to_edit.feature
  64. 2
      features/work_packages/preview.feature
  65. 2
      features/work_packages/update.feature
  66. 63
      features/work_packages/work_package_show.feature
  67. 2
      features/work_packages/work_package_textile_link.feature
  68. 13
      lib/associations_mapper.rb
  69. 2
      lib/open_project/version.rb
  70. 2
      lib/redmine.rb
  71. 9
      lib/redmine/views/my_page/block.rb
  72. 2
      spec/controllers/api/v2/planning_element_journals_controller_spec.rb
  73. 22
      spec/controllers/api/v2/planning_elements_controller_spec.rb
  74. 4
      spec/controllers/work_packages/moves_controller_spec.rb
  75. 34
      spec/controllers/work_packages_controller_spec.rb
  76. 56
      spec/factories/planning_element_factory.rb
  77. 4
      spec/helpers/work_packages_helper_spec.rb
  78. 82
      spec/models/permitted_params_spec.rb
  79. 29
      spec/models/planning_element_status_spec.rb
  80. 28
      spec/models/project_spec.rb
  81. 20
      spec/models/timelines_project_spec.rb
  82. 2
      spec/models/work_package_acts_as_journalized_spec.rb
  83. 8
      spec/models/work_package_nested_set_spec.rb
  84. 109
      spec/models/work_package_planning_spec.rb
  85. 6
      spec/models/work_package_reschedule_after_spec.rb
  86. 5
      spec/models/work_package_spec.rb
  87. 27
      spec/models/work_package_status_spec.rb
  88. 35
      spec/routing/repositories_routing_spec.rb
  89. 41
      spec/views/api/v2/planning_elements/_planning_element_api_rsb_spec.rb
  90. 2
      spec/views/api/v2/planning_elements/destroy_api_rsb_spec.rb
  91. 6
      spec/views/api/v2/planning_elements/index_api_rsb_spec.rb
  92. 2
      spec/views/api/v2/planning_elements/show_api_rsb_spec.rb
  93. 10
      test/functional/repositories_filesystem_controller_test.rb
  94. 18
      test/functional/repositories_git_controller_test.rb
  95. 30
      test/functional/repositories_subversion_controller_test.rb
  96. 38
      test/functional/wiki_controller_test.rb

@ -52,7 +52,7 @@ if (typeof []._reverse == 'undefined') {
}
jQuery(document).ajaxError(function(event, request, settings) {
if (request.status === 401 && /Reason: login needed/.match(request.getAllResponseHeaders())) {
if (request.status === 401 && /X-Reason: login needed/.match(request.getAllResponseHeaders())) {
if (confirm(I18n.t("js.logoff") + "\r\n" + I18n.t("js.redirect_login"))) {
location.href = openProject.loginUrl + "?back_url=" + encodeURIComponent(location.href);
}
@ -488,21 +488,29 @@ document.observe("dom:loaded", function() {
onCreate: function(request){
var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
if (!request.options.requestHeaders) {
request.options.requestHeaders = {};
}
if (csrf_meta_tag) {
var header = 'X-CSRF-Token',
token = csrf_meta_tag.readAttribute('content');
if (!request.options.requestHeaders) {
request.options.requestHeaders = {};
}
request.options.requestHeaders[header] = token;
}
request.options.requestHeaders['X-Authentication-Scheme'] = "Session";
if ($('ajax-indicator') && Ajax.activeRequestCount > 0) {
Element.show('ajax-indicator');
}
},
onComplete: function(){
onComplete: function(request, result){
if (result.status === 401 && /X-Reason: login needed/.match(result.getAllResponseHeaders())) {
if (confirm(I18n.t("js.logoff") + "\r\n" + I18n.t("js.redirect_login"))) {
location.href = openProject.loginUrl + "?back_url=" + encodeURIComponent(location.href);
}
}
if ($('ajax-indicator') && Ajax.activeRequestCount == 0) {
Element.hide('ajax-indicator');
}

@ -49,13 +49,17 @@ jQuery.fn.reverse = [].reverse;
$.fn.breadcrumbOutOfBounds = function(){
var lastElement = this.find(' > li').last();
var rightCorner = lastElement.width() + lastElement.offset().left;
var windowSize = jQuery(window).width();
if (lastElement) {
var rightCorner = lastElement.width() + lastElement.offset().left;
var windowSize = jQuery(window).width();
if ((Math.max(1000,windowSize) - rightCorner) < 10) {
return true;
}
else {
if ((Math.max(1000,windowSize) - rightCorner) < 10) {
return true;
}
else {
return false;
}
} else {
return false;
}
};

@ -11,382 +11,166 @@
var ModalHelper = (function() {
var ModalHelper = function(timeline, options) {
this.options = options;
this.timeline = timeline;
}
/** display the loading modal (spinner in a box)
* also fix z-index so it is always on top.
*/
ModalHelper.prototype.showLoadingModal = function() {
jQuery('#ajax-indicator').show().css('zIndex', 1020);
};
/** hide the loading modal */
ModalHelper.prototype.hideLoadingModal = function() {
jQuery('#ajax-indicator').hide();
};
/** submit a form in the background.
* @param form: form element
* @param url: url to submit to. can be undefined if so, url is taken from form.
* @param callback: called with results
*/
//TODO fix this inconsistency w/ optional url.
ModalHelper.prototype.submitBackground = function(form, url, callback) {
var data = form.serialize();
var ModalHelper = function() {
this._firstLoad = true;
var modalHelper = this;
var modalDiv, modalIframe;
if (typeof url === 'function') {
callback = url;
url = undefined;
}
function modalFunction(e) {
if (!e.ctrlKey && !e.metaKey) {
if (jQuery(e.target).attr("href")) {
url = jQuery(e.target).attr("href");
}
if (typeof url === 'undefined') {
url = form.attr('action');
}
if (url) {
e.preventDefault();
jQuery.ajax({
type: 'POST',
url: url,
data: data,
error: function(obj, error) {
callback(obj.status, obj.responseText);
},
success: function(response) {
callback(null, response);
modalHelper.createModal(url, function (modalDiv) {});
}
}
});
};
/** create a planning modal
* @param type either new, edit or show.
* @param projectId id of the project to create the modal for.
* @param elementId element id to create the modal for. not needed for new type.
* @param callback called when done
*/
ModalHelper.prototype.createPlanningModal = function(type, projectId, elementId, callback) {
var modalHelper = this;
var timeline = modalHelper.timeline;
var non_api_url = modalHelper.options.url_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/planning_elements/';
var api_url = modalHelper.options.url_prefix +
modalHelper.options.api_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/planning_elements/';
if (typeof elementId === 'function') {
callback = elementId;
elementId = undefined;
}
// in the following lines we create the url to get the data from
// also we create the url we submit the data to for the edit action.
//TODO: escape projectId and elementId.
if (type === 'new') {
jQuery(document).ready(function () {
var body = jQuery(document.body);
// whatever globals there are, they need to be added to the
// prototype, so that all ModalHelper instances can share them.
if (ModalHelper._done !== true) {
// one time initialization
modalDiv = jQuery('<div/>').css('hidden', true).attr('id', 'modalDiv');
body.append(modalDiv);
non_api_url += 'new.js';
/** replace all data-modal links and all inside modal links */
body.on("click", "[data-modal]", modalFunction);
modalDiv.on("click", "a", modalFunction);
} else if (type === 'edit') {
if (typeof elementId === 'string' || typeof elementId === 'number') {
non_api_url += elementId + '/edit.js';
api_url += elementId + '.json';
// close when body is clicked
body.on("click", ".ui-widget-overlay", jQuery.proxy(modalHelper.close, modalHelper));
ModalHelper._done = true;
} else {
throw new Error('need an element id for editing.');
modalDiv = jQuery('#modalDiv');
modalIframe = jQuery('#modalIframe');
}
} else if (type === 'show') {
if (typeof elementId === 'string' || typeof elementId === 'number') {
non_api_url += elementId + '.js';
} else {
modalHelper.modalDiv = modalDiv;
modalHelper.modalIframe = modalIframe;
});
throw new Error('need an element id for showing.');
this.loadingModal = false;
};
ModalHelper.prototype.tweakLink = function (url) {
if (url) {
if (url.indexOf("?layout=false") == -1 && url.indexOf("&layout=false") == -1) {
return url + (url.indexOf('?') != -1 ? "&layout=false" : "?layout=false");
} else {
return url;
}
} else {
throw new Error('invalid action. allowed: new, show, edit');
}
};
//create the modal by using the html the url gives us.
modalHelper.createModal(non_api_url, function(ele) {
var projects = timeline.projects;
var project;
var projectSelect;
var fields = ele.find(':input');
ele.data('changed', false);
ModalHelper.prototype.iframeLoadHandler = function () {
try {
var modalDiv = this.modalDiv, modalIframe = this.modalIframe, modalHelper = this;
var submitFunction = function(e) {
modalHelper.showLoadingModal();
var content = modalIframe.contents();
var body = content.find("body");
if (type === 'new') {
if (body.html() !== "") {
this.hideLoadingModal();
this.loadingModal = false;
api_url = modalHelper.options.url_prefix +
modalHelper.options.api_prefix +
modalHelper.options.project_prefix +
"/" +
projectSelect.val() +
'/planning_elements.json';
}
modalDiv.data('changed', false);
modalHelper.submitBackground(jQuery(this), api_url, function(err, res) {
var element;
modalHelper.hideLoadingModal();
// display errors correctly.
if (!err) {
currentURL = '';
timeline.reload();
if (elementId === undefined) {
try {
// internet explorer has a text attribute instead of textContent.
element = res.getElementsByTagName('id')[0];
elementId = element.textContent || element.text;
} catch (e) {
console.log(e);
}
}
if (elementId !== undefined) {
modalHelper.createPlanningModal('show', projectId, elementId);
}
} else if (err !== '500') {
ele.find('.errorExplanation').remove();
var error = jQuery('<div>').attr('class', 'errorExplanation').attr('id', 'errorExplanation');
var json = jQuery.parseJSON(res);
var i, errorSpan, errorFormEle;
var errorField;
for (errorField in json.errors) {
if (json.errors.hasOwnProperty(errorField)) {
//for (i = 0; i < json.errors.length; i += 1) {
error.append(
jQuery('<ul/>').append(
jQuery('<li/>').text(I18n.t('js.timelines.' + errorField) + ' ' + json.errors[errorField]))
);
try {
errorSpan = jQuery('<span/>').attr('class', 'errorSpan');
errorFormEle = jQuery('#planning_element_' + errorField);
errorFormEle.before(errorSpan);
errorSpan.append(errorFormEle);
} catch (e) {
// nop
}
}
}
ele.prepend(error);
ele.scrollTop(0);
body.on("click", "a", function (e) {
var url = jQuery(e.target).attr("href");
if (url) {
jQuery(e.target).attr("href", modalHelper.tweakLink(url));
}
});
if (e) {
e.preventDefault();
}
};
//if we want to create a new element, the project must be selectable.
if (type === 'new') {
body.on("submit", "form", function (e) {
var url = jQuery(e.target).attr("action");
ele.find('tbody').first().prepend(
jQuery('<tr><th>' + I18n.t('js.timelines.create_planning_select_project') + '</th><td><select id="projectSelect"/></td></tr>')
);
projectSelect = ele.find('#projectSelect');
for (project in projects) {
if (projects.hasOwnProperty(project)) {
if (projects[project].permissions.edit_planning_elements === true) {
projectSelect.append(jQuery('<option/>').attr('value', projects[project].identifier).text(projects[project].name));
}
if (url) {
jQuery(e.target).attr("action", modalHelper.tweakLink(url));
}
}
projectSelect.change(function() {
var planningElementName = ele.find('#planning_element_name').val();
var planningElementDescription = ele.find('#planning_element_description').val();
var planningElementType = ele.find('#planning_element_planning_element_type_id').val();
var planningElementResponsible = ele.find('#planning_element_responsible_id').val();
var planningElementStartDate = ele.find('#planning_element_start_date').val();
var planningElementEndDate = ele.find('#planning_element_end_date').val();
//just overwrite the current planning modal.
modalHelper.createPlanningModal('new', projectSelect.val(), function(ele) {
ele.find('#planning_element_name').val(planningElementName);
ele.find('#planning_element_description').val(planningElementDescription);
ele.find('#planning_element_planning_element_type_id').val(planningElementType);
ele.find('#planning_element_responsible_id').val(planningElementResponsible);
ele.find('#planning_element_start_date').val(planningElementStartDate);
ele.find('#planning_element_end_date').val(planningElementEndDate);
});
});
// set to given project id.
projectSelect.val(projectId);
}
//tweak body.
body.find("#footnotes_debug").hide();
body.css("min-width", "0px");
//create cancel and save button
if (type === 'new' || type === 'edit') {
var cancel = jQuery("<a>").addClass("icon").addClass("icon-cancel").text(I18n.t("js.timelines.cancel")).attr("href", "#").click(function (e) {
e.preventDefault();
if (ele.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
if (typeof elementId === "undefined") {
if (ele.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
ele.data('changed', false);
ele.dialog('close');
}
} else {
modalHelper.createPlanningModal('show', projectId, elementId);
}
}
body.find(":input").change(function () {
modalDiv.data('changed', true);
});
var save = jQuery("<a>").addClass("icon").addClass("icon-save").text(I18n.t("js.timelines.save")).attr("href", "#").click(function (e) {
e.preventDefault();
submitFunction.call(ele.find('form'));
});
jQuery(this).trigger("loaded");
ele.find('form').prepend(
jQuery("<div>").append(
cancel
).append(
save
).addClass("contextual")
);
//remove old submit/cancel elements
ele.find('form').find(':submit').css("display", "none");
ele.find('form').find('[name=cancelButton]').remove();
//make textareas bigger
if (ele.height() > 800) {
ele.find('textarea').attr("rows", 10);
} else if (ele.height() > 600) {
ele.find('textarea').attr("rows", 8);
}
}
modalDiv.parent().show();
//overwrite the action for the edit button.
if (type === 'show') {
modalIframe.attr("height", modalDiv.height());
} else {
this.showLoadingModal();
}
} catch (e) {
this.loadingModal = false;
this.hideLoadingModal();
this.close();
}
};
ele.find('.icon-edit').click(function(e) {
modalHelper.createPlanningModal('edit', projectId, elementId);
e.preventDefault();
});
/** display the loading modal (spinner in a box)
* also fix z-index so it is always on top.
*/
ModalHelper.prototype.showLoadingModal = function() {
jQuery('#ajax-indicator').show().css('zIndex', 1020);
};
ele.find('.icon-cancel').click(function(e) {
modalHelper.showLoadingModal();
/** hide the loading modal */
ModalHelper.prototype.hideLoadingModal = function() {
jQuery('#ajax-indicator').hide();
};
modalHelper.submitBackground(jQuery(ele.find('.icon-cancel').parent()[0]),
function(err, res) {
modalHelper.hideLoadingModal();
// display errors correctly.
if (!err) {
ele.dialog('close');
timeline.reload();
}
}
);
ModalHelper.prototype.rewriteIframe = function () {
this.destroyIframe();
this.modalIframe = this.writeIframe(this.modalDiv);
};
e.preventDefault();
});
ModalHelper.prototype.destroyIframe = function () {
if (this.modalIframe) {
this.modalIframe.remove();
}
};
ele.find('.icon-del').click(function(e) {
var tokenName, token, action, data = {};
var url = modalHelper.options.url_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/planning_elements/';
tokenName = jQuery('meta[name=csrf-param]').attr('content');
token = jQuery('meta[name=csrf-token]').attr('content');
if (jQuery(this).attr('href').indexOf("destroy") == -1) {
modalHelper.showLoadingModal();
action = 'delete';
data['_method'] = 'delete';
data[tokenName] = token;
jQuery.post(url + elementId + '/move_to_trash',
data,
function() {
modalHelper.hideLoadingModal();
ele.dialog('close');
timeline.reload();
}).error(function() {
modalHelper.hideLoadingModal();
alert(I18n.t('js.timelines.error'));
});
//move to bin
} else if (confirm(I18n.t('js.timelines.really_delete_planning_element'))) {
modalHelper.showLoadingModal();
action = 'delete';
data['_method'] = 'delete';
data[tokenName] = token;
data['commit'] = 'delete';
jQuery.post(url + elementId,
data,
function() {
modalHelper.hideLoadingModal();
ele.dialog('close');
timeline.reload();
}).error(function() {
modalHelper.hideLoadingModal();
alert(I18n.t('js.timelines.error'));
});
//move to bin
}
ModalHelper.prototype.writeIframe = function (div) {
modalIframe = jQuery('<iframe/>').attr("frameBorder", "0px").attr('id', 'modalIframe').attr('width', '100%').attr('height', '100%').attr('name', 'modalIframe');
div.append(modalIframe);
e.preventDefault();
});
}
modalIframe.bind("load", jQuery.proxy(this.iframeLoadHandler, this));
fields.change(function(e) {
ele.data('changed', true);
});
return modalIframe;
};
// calendar click must be stopped so it does not close the modal.
ele.find('.calendar-trigger').click(function() {
jQuery('.calendar').click(function(e) {
e.stopPropagation();
}).css('zIndex', 2000);
});
ModalHelper.prototype.close = function() {
var modalDiv = this.modalDiv;
if (!this.loadingModal) {
if (modalDiv && (modalDiv.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog')))) {
modalDiv.data('changed', false);
modalDiv.dialog('close');
// if a form is submitted, we stop it and submit it in the background.
ele.find('form').submit(submitFunction);
this.destroyIframe();
if (typeof callback === 'function') {
callback(ele);
jQuery(this).trigger("closed");
}
});
}
};
ModalHelper.prototype.loading = function() {
this.modalDiv.parent().hide();
this.loadingModal = true;
this.showLoadingModal();
};
/** create a modal dialog from url html data
@ -394,140 +178,45 @@ var ModalHelper = (function() {
* @param callback called when done. called with modal div.
*/
ModalHelper.prototype.createModal = function(url, callback) {
var modalHelper = this;
var modalHelper = this, modalDiv = this.modalDiv, counter = 0;
url = this.tweakLink(url);
if (modalHelper.loadingModal) {
return;
}
modalHelper.loadingModal = true;
try {
modalHelper.showLoadingModal();
// get html for url.
jQuery.ajax({
type: 'GET',
url: url,
dataType: 'html',
error: function(obj, error) {
modalHelper.hideLoadingModal();
modalHelper.loadingModal = false;
},
success: function(data) {
try {
modalHelper.hideLoadingModal();
currentURL = url;
var ta = modalHelper.modalDiv;
// write html to div
ta.html(data);
// show dialog.
ta.dialog({
modal: true,
resizable: false,
draggable: false,
width: '900px',
height: jQuery(window).height() * 0.8,
position: {
my: 'center',
at: 'center'
}
});
// close when cancel is clicked.
ta.find('[name=cancelButton]').click(function(e) {
e.preventDefault();
if (ta.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
ta.data('changed', false);
ta.dialog('close');
}
});
// hide dialog header
//TODO: we need a default close button somewhere
jQuery('#planningElementDialog').parent().prepend('<div id="ui-dialog-closer" />');
jQuery('.ui-dialog-titlebar').hide();
if (typeof callback === 'function') {
callback(ta);
}
} catch (e) {
console.log(e);
} finally {
modalHelper.loadingModal = false;
}
}
});
} catch (e) {
console.log(e);
modalHelper.loadingModal = false;
}
};
ModalHelper.prototype.setup = function() {
var body = jQuery('body');
var timeline = this.timeline;
var modalDiv;
// whatever globals there are, they need to be added to the
// prototype, so that all ModalHelper instances can share them.
if (ModalHelper.prototype.done !== true) {
// one time initialization
modalDiv = jQuery('<div/>').css('hidden', true).attr('id', 'planningElementDialog');
body.append(modalDiv);
// close when body is clicked
body.click(function(e) {
if (modalDiv.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
modalDiv.data('changed', false);
modalDiv.dialog('close');
} else {
e.stopPropagation();
}
});
// do not close when element is clicked
modalDiv.click(function(e) {
e.stopPropagation();
});
ModalHelper.prototype.done = true;
var calculatedHeight = jQuery(window).height() * 0.8;
this.modalIframe = modalHelper.writeIframe(modalDiv);
modalDiv.attr("height", calculatedHeight);
this.modalIframe.attr("height", calculatedHeight);
modalDiv.dialog({
modal: true,
resizable: false,
draggable: false,
width: '900px',
height: calculatedHeight,
position: {
my: 'center',
at: 'center'
},
closeOnEscape: false
});
} else {
if (this._firstLoad) {
//add closer
modalDiv.parent().prepend('<div id="ui-dialog-closer" />').click(jQuery.proxy(this.close, this));
jQuery('.ui-dialog-titlebar').hide();
modalDiv = jQuery('#planningElementDialog');
this._firstLoad = false;
}
// every-time initialization
jQuery(timeline).on('dataLoaded', function() {
var projects = timeline.projects;
var project;
this.loading();
for (project in projects) {
if (projects.hasOwnProperty(project)) {
if (projects[project].permissions.edit_planning_elements === true) {
jQuery('#newPlanning').show();
break;
}
}
}
});
var loadingModalDiv = jQuery('<div/>');
body.append(loadingModalDiv);
loadingModalDiv.css('hidden', true).attr('id', 'loadingModal');
this.loadingModal = false;
this.modalDiv = modalDiv;
}
this.modalIframe.attr("src", url);
};
return ModalHelper;
})();

@ -274,7 +274,8 @@ Timeline = {
// prerequisites (3rd party libs)
this.checkPrerequisites();
this.modalHelper = new ModalHelper(
this.modalHelper = new ModalHelper();
this.modalHelper.setupTimeline(
this,
{
api_prefix : this.options.api_prefix,
@ -282,7 +283,10 @@ Timeline = {
project_prefix : this.options.project_prefix
}
);
this.modalHelper.setup();
jQuery(this.modalHelper).on("closed", function () {
timeline.reload();
});
timelineLoader = new Timeline.TimelineLoader(
this,
@ -1036,13 +1040,13 @@ Timeline = {
TimelineLoader.prototype.registerPlanningElementsByID = function (ids) {
this.inChunks(ids, function (planningElementIdsOfPacket, i) {
var planningElementPrefix = this.options.url_prefix +
this.options.planning_element_prefix;
var projectPrefix = this.options.url_prefix +
this.options.api_prefix;
// load current planning elements.
this.loader.register(
Timeline.PlanningElement.identifier + '_IDS_' + i,
{ url : planningElementPrefix +
{ url : projectPrefix +
'/planning_elements.json?ids=' +
planningElementIdsOfPacket.join(',')},
{ storeIn: Timeline.PlanningElement.identifier }
@ -3344,7 +3348,7 @@ Timeline = {
PE_TEXT_OUTSIDE_PADDING: 6, // space between planning element and text to its right.
PE_TEXT_SCALE: 0.1875, // 64 * (1/8 * 1.5) = 12
USE_MODALS: false,
USE_MODALS: true,
scale: 1,
zoomIndex: 0,
@ -3585,14 +3589,21 @@ Timeline = {
var timeline = this;
return {
all: ['end_date', 'planning_element_types', 'project_status', 'project_type', 'responsible', 'start_date'],
planning_element_types: function(data, pet, pt) {
if (pet === undefined) {
// nop
} else if (pet === null) {
return jQuery('<span class="tl-column">-</span>');
} else {
return jQuery('<span class="tl-column">' + timeline.escape(pet.name) + '</span>');
type: function (data, pet, pt) {
var ptName, petName;
if (pt !== undefined) {
if (pt !== null) {
ptName = pt.name;
}
}
if (pet !== undefined) {
if (pet !== null) {
petName = pet.name;
}
}
return jQuery('<span class="tl-column">' + (ptName || petName || "-") + '</span>');
},
project_status: function(data) {
var status;
@ -3605,15 +3616,6 @@ Timeline = {
return jQuery('<span class="tl-column">-</span>');
}
},
project_type: function(data, pet, pt) {
if (pt === undefined) {
// nop
} else if (pt === null) {
return jQuery('<span class="tl-column">-</span>');
} else {
return jQuery('<span class="tl-column">' + timeline.escape(pt.name) + '</span>');
}
},
responsible: function(data) {
var result;
if (data.responsible && data.responsible.name) {
@ -4175,17 +4177,7 @@ Timeline = {
text = timeline.escape(data.name);
if (data.getUrl instanceof Function) {
text = jQuery('<a href="' + data.getUrl() + '" class="tl-discreet-link" target="_blank"/>').append(text).attr("title", text);
text.click(function(event) {
if (Timeline.USE_MODALS && !event.ctrlKey && !event.metaKey && data.is(Timeline.PlanningElement)) {
timeline.modalHelper.createPlanningModal(
'show',
data.project.identifier,
data.id
);
event.preventDefault();
}
});
text = jQuery('<a href="' + data.getUrl() + '" class="tl-discreet-link" target="_blank" data-modal/>').append(text).attr("title", text);
}
if (data.is(Timeline.Project)) {
@ -4868,11 +4860,7 @@ Timeline = {
e.click(function(e) {
if (Timeline.USE_MODALS) {
var payload = node.getData();
timeline.modalHelper.createPlanningModal(
'show',
payload.project.identifier,
payload.id
);
timeline.modalHelper.createModal(payload.getUrl());
e.stopPropagation();
}
});
@ -4905,7 +4893,7 @@ Timeline = {
}
if (typeof projectID !== "undefined") {
this.modalHelper.createPlanningModal("new", projectID);
this.modalHelper.create(projectID);
}
}
};

@ -0,0 +1,43 @@
//-- 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.
//++
/** create a work package creation modal
* @param projectId id of the project to create the modal for.
*/
ModalHelper.prototype.create = function(projectId) {
var modalHelper = this;
var url = modalHelper.options.url_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/work_packages/new';
//create the modal by using the html the url gives us.
modalHelper.createModal(url);
};
ModalHelper.prototype.setupTimeline = function(timeline, options) {
this.timeline = timeline;
this.options = options;
// every-time initialization
jQuery(timeline).on('dataLoaded', function() {
var projects = timeline.projects;
var project;
for (project in projects) {
if (projects.hasOwnProperty(project)) {
if (projects[project].permissions.edit_planning_elements === true) {
jQuery('#newPlanning').show();
break;
}
}
}
});
};

@ -1,4 +1,4 @@
//-- copyright
/*-- copyright
// OpenProject is a project management system.
//
// Copyright (C) 2012-2013 the OpenProject Team
@ -7,7 +7,7 @@
// modify it under the terms of the GNU General Public License version 3.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
*/
/*
* This is a manifest file that'll automatically include all the stylesheets available in this directory

@ -1042,6 +1042,7 @@ a.close-icon:hover {
.icon-folder { background-image: url(<%= asset_path 'folder.png' %>); }
.open .icon-folder { background-image: url(<%= asset_path 'folder_open.png' %>); }
.icon-package { background-image: url(<%= asset_path 'package.png' %>); }
.icon-parent { background-image: url(<%= asset_path 'link.png' %>); }
.icon-user { background-image: url(<%= asset_path 'user.png' %>); }
.icon-projects { background-image: url(<%= asset_path 'latest_projects.png' %>); }
.icon-help { background-image: url(<%= asset_path 'help.png' %>); }
@ -2249,6 +2250,15 @@ tr.context-menu-selection td.priority {
background: none !important;
}
.nomenus #content {
margin: 0px;
padding: 0px;
}
.nomenus #main {
padding-bottom: 0px;
}
/* Blue dots killed */
.nosidebar ul li {
background: none;
@ -3388,3 +3398,16 @@ input::-webkit-input-placeholder {
:-moz-placeholder {
color: #000000;
}
.ui-dialog { overflow: visible; }
#ui-dialog-closer {
background-image: url(<%= asset_path 'modal_close.png' %>);
cursor: pointer;
height: 33px;
position: absolute;
right: -15px;
top: -15px;
width: 33px;
z-index: 5000;
}

@ -1,4 +1,4 @@
//-- copyright
/*-- copyright
// OpenProject is a project management system.
//
// Copyright (C) 2012-2013 the OpenProject Team
@ -7,7 +7,7 @@
// modify it under the terms of the GNU General Public License version 3.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
*/
.jstEditor {
padding-left: 0px;

@ -1,13 +1,13 @@
//-- 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.
//++
/*-- 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.
*/
.project-search-results {
-webkit-box-sizing: content-box; /* Safari/Chrome, other WebKit */

@ -544,19 +544,6 @@ dt.timelines\2F planning-element {
background-image: url(../assets/new_planning_element.png);
}
.ui-dialog { overflow: visible; }
#ui-dialog-closer {
background-image: url("../assets/modal_close.png");
cursor: pointer;
height: 33px;
position: absolute;
right: -15px;
top: -15px;
width: 33px;
z-index: 5000;
}
#history .journal {
background: none;
}

@ -73,7 +73,7 @@ module Api
end
def show
@planning_element = @project.planning_elements.find(params[:id])
@planning_element = @project.work_packages.find(params[:id])
respond_to do |format|
format.api
@ -157,7 +157,7 @@ module Api
# remove this and replace by calls it with calls
# to assign_planning_elements once WorkPackages can be created
def planning_element_scope
@project.planning_elements.without_deleted
@project.work_packages.without_deleted
end
# Helpers

@ -82,6 +82,10 @@ class ApplicationController < ActionController::Base
require "repository/#{scm.underscore}"
end
def default_url_options(options={})
{ :layout => params["layout"] }
end
# the current user is a per-session kind of thing and session stuff is controller responsibility.
# a globally accessible User.current is a big code smell. when used incorrectly it allows getting
# the current user outside of a session scope, i.e. in the model layer, from mailers or in the console
@ -203,7 +207,7 @@ class ApplicationController < ActionController::Base
end
format.any(:xml, :js, :json) {
head :unauthorized,
"Reason" => "login needed",
"X-Reason" => "login needed",
'WWW-Authenticate' => authentication_scheme + ' realm="OpenProject API"'
}
end

@ -18,13 +18,13 @@ class MyController < ApplicationController
menu_item :account, :only => [:account]
menu_item :password, :only => [:password]
BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_work_packages,
DEFAULT_BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_work_packages,
'issuesreportedbyme' => :label_reported_work_packages,
'issueswatched' => :label_watched_work_packages,
'news' => :label_news_latest,
'calendar' => :label_calendar,
'timelog' => :label_spent_time
}.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
}.freeze
DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
'right' => ['issuesreportedbyme']
@ -33,6 +33,11 @@ class MyController < ApplicationController
verify :xhr => true,
:only => [:add_block, :remove_block, :order_blocks]
def self.available_blocks
@available_blocks ||= DEFAULT_BLOCKS.merge(Redmine::Views::MyPage::Block.additional_blocks)
end
# Show user's page
def index
@user = User.current
@ -131,7 +136,7 @@ class MyController < ApplicationController
@user = User.current
@blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
@block_options = []
BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
MyController.available_blocks.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
end
# Add a block to user's page
@ -139,7 +144,7 @@ class MyController < ApplicationController
# params[:block] : id of the block to add
def add_block
block = params[:block].to_s.underscore
(render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
(render :nothing => true; return) unless block && (MyController.available_blocks.keys.include? block)
@user = User.current
layout = @user.pref[:my_page_layout] || {}
# remove if already present in a group

@ -37,6 +37,6 @@ class PlanningElementJournalsController < ApplicationController
def find_planning_element_by_planning_element_id
raise ActiveRecord::RecordNotFound if @project.blank?
@planning_element = @project.planning_elements.find(params[:planning_element_id])
@planning_element = @project.work_packages.find(params[:planning_element_id])
end
end

@ -229,8 +229,13 @@ class RepositoriesController < ApplicationController
@project = Project.find(params[:id])
@repository = @project.repository
(render_404; return false) unless @repository
@path = params[:path].join('/') unless params[:path].nil?
@path ||= ''
@path = ''
if params.has_key? :path
format_valid = params.has_key?(:format) && params[:format] != 'raw' && params[:format] != 'diff'
@path = params[:path]
@path += ".#{params[:format]}" if format_valid
end
@rev = params[:rev].blank? ? @repository.default_branch : params[:rev].to_s.strip
@rev_to = params[:rev_to]

@ -30,7 +30,16 @@ require 'htmldiff'
class WikiController < ApplicationController
default_search_scope :wiki_pages
before_filter :find_wiki, :authorize
before_filter :find_existing_page, :only => [:rename, :protect, :history, :diff, :annotate, :add_attachment, :list_attachments, :destroy]
before_filter :find_existing_page, :only => [:edit_parent_page,
:update_parent_page,
:rename,
:protect,
:history,
:diff,
:annotate,
:add_attachment,
:list_attachments,
:destroy]
verify :method => :post, :only => [:protect], :redirect_to => { :action => :show }
verify :method => :get, :only => [:new, :new_child], :render => {:nothing => true, :status => :method_not_allowed}
@ -100,7 +109,7 @@ class WikiController < ApplicationController
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, :params => params, :page => @page)
redirect_to :action => 'show', :project_id => @project, :id => @page.title
redirect_to_show
else
render :action => 'new'
end
@ -169,7 +178,7 @@ class WikiController < ApplicationController
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
# don't save if text wasn't changed
redirect_to :action => 'show', :project_id => @project, :id => @page.title
redirect_to_show
return
end
params[:content].delete(:version) # The version count is automatically increased
@ -181,7 +190,7 @@ class WikiController < ApplicationController
attachments = Attachment.attach_files(@page, params[:attachments])
render_attachment_warning_if_needed(@page)
call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
redirect_to :action => 'show', :project_id => @project, :id => @page.title
redirect_to_show
else
render :action => 'edit'
end
@ -200,13 +209,30 @@ class WikiController < ApplicationController
@original_title = @page.pretty_title
if request.put? && @page.update_attributes(params[:wiki_page])
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'show', :project_id => @project, :id => @page.title
redirect_to_show
end
end
def edit_parent_page
return render_403 unless editable?
@parent_pages = @wiki.pages.all(:include => :parent) - @page.self_and_descendants
end
def update_parent_page
return render_403 unless editable?
@page.parent_id = params[:wiki_page][:parent_id]
if @page.save
flash[:notice] = l(:notice_successful_update)
redirect_to_show
else
@parent_pages = @wiki.pages.all(:include => :parent) - @page.self_and_descendants
render 'edit_parent_page'
end
end
def protect
@page.update_attribute :protected, params[:protected]
redirect_to :action => 'show', :project_id => @project, :id => @page.title
redirect_to_show
end
# show page history
@ -343,4 +369,8 @@ private
def default_breadcrumb
Wiki.name.humanize
end
def redirect_to_show
redirect_to :action => 'show', :project_id => @project, :id => @page.title
end
end

@ -32,7 +32,20 @@ class WorkPackages::MovesController < ApplicationController
JournalManager.add_journal work_package, User.current, @notes || ""
call_hook(:controller_work_packages_move_before_save, { :params => params, :work_package => work_package, :target_project => @target_project, :copy => !!@copy })
if r = work_package.move_to_project(@target_project, new_type, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params)})
permitted_params = params.permit(:copy,
:assigned_to_id,
:responsible_id,
:start_date,
:due_date,
:priority_id,
:follow,
:new_type_id,
:new_project_id,
ids:[],
status_id:[])
if r = work_package.move_to_project(@target_project, new_type, {:copy => @copy, :attributes => permitted_params})
moved_work_packages << r
else
unsaved_work_package_ids << work_package.id
@ -92,14 +105,4 @@ class WorkPackages::MovesController < ApplicationController
@notes ||= ''
end
def extract_changed_attributes_for_move(params)
changed_attributes = {}
[:assigned_to_id, :responsible_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute|
unless params[valid_attribute].blank?
changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute])
end
end
changed_attributes
end
end

@ -26,8 +26,6 @@ class WorkPackagesController < ApplicationController
wp = controller.work_package
case wp
when PlanningElement
:planning_elements
when Issue
:issues
end
@ -296,8 +294,6 @@ class WorkPackagesController < ApplicationController
sti_type = params[:sti_type] || params[:work_package][:sti_type] || 'Issue'
wp = case sti_type
when PlanningElement.to_s
project.add_planning_element(permitted)
when Issue.to_s
project.add_issue(permitted)
else

@ -385,7 +385,7 @@ module ApplicationHelper
end
def to_path_param(path)
path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
path.to_s
end

@ -46,13 +46,11 @@ module PlanningElementsHelper
api.planning_element_type(:id => type.id, :name => type.name)
end
if planning_element.planning_element_status
status = planning_element.planning_element_status
if planning_element.status
status = planning_element.status
api.planning_element_status(:id => status.id, :name => status.name)
end
api.planning_element_status_comment(planning_element.planning_element_status_comment)
if include_journals?
api.array :journals, :size => planning_element.journals.size do
planning_element.journals.each do |journal|

@ -1,26 +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 PlanningElement < WorkPackage
unloadable
include ActiveModel::ForbiddenAttributesProtection
accepts_nested_attributes_for_apis_for :parent,
:planning_element_status,
:type,
:project
scope :for_projects, lambda { |projects|
{:conditions => {:project_id => projects}}
}
end

@ -114,9 +114,6 @@ class Project < ActiveRecord::Base
has_many :timelines, :class_name => "::Timeline",
:dependent => :destroy
has_many :planning_elements, :class_name => "::PlanningElement",
:dependent => :destroy
has_many :reportings_via_source, :class_name => "::Reporting",
:foreign_key => 'project_id',
:dependent => :delete_all
@ -203,10 +200,6 @@ class Project < ActiveRecord::Base
end
end
def has_many_dependent_for_planning_elements
planning_elements.each {|element| element.destroy!}
end
def self.selectable_projects
Project.visible.select {|p| User.current.member_of? p }.sort_by(&:to_s)
end
@ -862,15 +855,6 @@ class Project < ActiveRecord::Base
list
end
# TODO: merge with add_issue once type or similar is defined there
def add_planning_element(attributes = {})
attributes ||= {}
self.planning_elements.build do |pe|
pe.attributes = attributes
end
end
# TODO: merge with add_planning_elemement once type or similar is defined there
def add_issue(attributes = {})
attributes ||= {}

@ -40,7 +40,7 @@ class Repository::Subversion < Repository
# Returns a path relative to the url of the repository
def relative_path(path)
path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}\/"), '')
end
def fetch_changesets

@ -84,8 +84,7 @@ class Timeline < ActiveRecord::Base
]
@@available_columns = [
"project_type",
"planning_element_types",
"type",
"start_date",
"end_date",
"responsible",

@ -35,9 +35,6 @@ class Type < ActiveRecord::Base
belongs_to :color, :class_name => 'PlanningElementTypeColor',
:foreign_key => 'color_id'
has_many :planning_elements, :class_name => 'PlanningElement',
:foreign_key => 'type_id',
:dependent => :nullify
acts_as_list
validates_presence_of :name

@ -14,6 +14,7 @@
# So we create an 'emtpy' Issue class first, to make Project happy.
class WorkPackage < ActiveRecord::Base
include WorkPackage::Validations
include WorkPackage::SchedulingRules
include WorkPackage::StatusTransitions
@ -42,9 +43,6 @@ class WorkPackage < ActiveRecord::Base
belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
belongs_to :planning_element_status, :class_name => "PlanningElementStatus",
:foreign_key => 'planning_element_status_id'
has_many :time_entries, :dependent => :delete_all
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
@ -62,6 +60,8 @@ class WorkPackage < ActiveRecord::Base
scope :without_deleted, :conditions => "#{WorkPackage.quoted_table_name}.deleted_at IS NULL"
scope :deleted, :conditions => "#{WorkPackage.quoted_table_name}.deleted_at IS NOT NULL"
scope :in_status, lambda {|*args| where(:status_id => (args.first.respond_to?(:id) ? args.first.id : args.first))}
scope :for_projects, lambda { |projects|
{:conditions => {:project_id => projects}}
}
@ -131,6 +131,16 @@ class WorkPackage < ActiveRecord::Base
acts_as_attachable :after_add => :attachments_changed,
:after_remove => :attachments_changed
# Mapping attributes, that are passed in as id's onto their respective associations
# (eg. type=4711 onto type=Type.find(4711))
include AssociationsMapper
# recovered this from planning-element: is it still needed?!
map_associations_for :parent,
:status,
:type,
:project,
:priority
# This one is here only to ease reading
module JournalizedProcs
def self.event_title
@ -590,8 +600,10 @@ class WorkPackage < ActiveRecord::Base
end
# Allow bulk setting of attributes on the work_package
if options[:attributes]
work_package.attributes = options[:attributes]
end
# before setting the attributes, we need to remove the move-related fields
work_package.attributes = options[:attributes].except(:copy,:new_project_id, :new_type_id, :follow, :ids)
.reject { |key, value| value.blank? }
end # FIXME this eliminates the case, where values shall be bulk-assigned to null, but this needs to work together with the permit
if options[:copy]
work_package.author = User.current
work_package.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
@ -601,6 +613,7 @@ class WorkPackage < ActiveRecord::Base
self.status
end
end
if work_package.save
unless options[:copy]
# Manually update project_id on related time entries

@ -30,43 +30,46 @@ See doc/COPYRIGHT.rdoc for more details.
<%= content_for(:header_tags) if content_for?(:header_tags) %>
</head>
<noscript><%=l(:description_noscript)%></noscript>
<% show_decoration = params["layout"].nil? %>
<body class="<%= body_css_classes %>">
<div id="wrapper">
<div id="top-menu">
<div id="header">
<div id="logo"></div>
<div id="top-menu-items">
<div id="search">
<%= label_tag("q", l(:label_search), :class => "hidden-for-sighted") %>
<%= form_tag({:controller => '/search', :action => 'index', :project_id => @project}, :method => :get) do %>
<%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
<div id='search_wrap'>
<%= text_field_tag 'q', @question, :size => 20, :class => 'search_field', :placeholder => l(:search_input_placeholder), :accesskey => accesskey(:quick_search) %>
</div>
<% end %>
</div>
<% if show_decoration %>
<div id="top-menu">
<div id="header">
<div id="logo"></div>
<div id="top-menu-items">
<div id="search">
<%= label_tag("q", l(:label_search), :class => "hidden-for-sighted") %>
<%= form_tag({:controller => '/search', :action => 'index', :project_id => @project}, :method => :get) do %>
<%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %>
<div id='search_wrap'>
<%= text_field_tag 'q', @question, :size => 20, :class => 'search_field', :placeholder => l(:search_input_placeholder), :accesskey => accesskey(:quick_search) %>
</div>
<% end %>
</div>
<h1 class="hidden-for-sighted">
<%= l(:label_top_menu) %>
</h1>
<h1 class="hidden-for-sighted">
<%= l(:label_top_menu) %>
</h1>
<%= render_top_menu %>
<%= render_top_menu %>
</div>
</div>
</div>
<div id="breadcrumb">
<h1>
<%= you_are_here_info %>
<%= full_breadcrumb %>
</h1>
<div id="breadcrumb">
<h1>
<%= you_are_here_info %>
<%= full_breadcrumb %>
</h1>
</div>
<%= javascript_tag('jQuery(function($) { $("div#breadcrumb ul.breadcrumb").adjustBreadcrumbToWindowSize(); });') %>
</div>
<%= javascript_tag('jQuery(function($) { $("div#breadcrumb ul.breadcrumb").adjustBreadcrumbToWindowSize(); });') %>
</div>
<% end %>
<% main_menu = render_main_menu(@project) %>
<% side_displayed = content_for?(:sidebar) || content_for?(:main_menu) || !main_menu.blank? %>
<div id="main" class="<%= side_displayed ? '' : "nosidebar" %>">
<% if (side_displayed) %>
<div id="main" class="<%= side_displayed ? '' : "nosidebar" %><%= (show_decoration) ? '' : 'nomenus' %>">
<% if (side_displayed && show_decoration) %>
<div id="main-menu">
<h1 class="hidden-for-sighted"><%= l(:label_main_menu) %></h1>
<div id="toggle-project-menu">
@ -85,7 +88,7 @@ See doc/COPYRIGHT.rdoc for more details.
<%= expand_current_menu %>
<% end %>
<div class="<%= side_displayed ? '' : "nosidebar" %>" id="content">
<div class="<%= (side_displayed) ? '' : "nosidebar" %>" id="content">
<h1 class="hidden-for-sighted"><%= l(:label_content) %></h1>
<%= render_flash_messages %>
@ -98,9 +101,11 @@ See doc/COPYRIGHT.rdoc for more details.
</div>
</div>
<div id="footer">
<%= footer_content %>
</div>
<% if (show_decoration) %>
<div id="footer">
<%= footer_content %>
</div>
<% end %>
<div id="ajax-indicator" style="display:none;"><span><%= l(:label_loading) %></span></div>

@ -19,7 +19,7 @@ See doc/COPYRIGHT.rdoc for more details.
<div id="list-top">
<% @blocks['top'].each do |b|
next unless MyController::BLOCKS.keys.include? b %>
next unless MyController.available_blocks.keys.include? b %>
<div class="mypage-box">
<%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
</div>
@ -28,7 +28,7 @@ See doc/COPYRIGHT.rdoc for more details.
<div id="list-left" class="splitcontentleft">
<% @blocks['left'].each do |b|
next unless MyController::BLOCKS.keys.include? b %>
next unless MyController.available_blocks.keys.include? b %>
<div class="mypage-box">
<%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
</div>
@ -37,7 +37,7 @@ See doc/COPYRIGHT.rdoc for more details.
<div id="list-right" class="splitcontentright">
<% @blocks['right'].each do |b|
next unless MyController::BLOCKS.keys.include? b %>
next unless MyController.available_blocks.keys.include? b %>
<div class="mypage-box">
<%= render :partial => "my/blocks/#{b}", :locals => { :user => @user } %>
</div>

@ -66,21 +66,21 @@ function removeBlock(block) {
<div id="list-top" class="block-receiver">
<% @blocks['top'].each do |b|
next unless MyController::BLOCKS.keys.include? b %>
next unless MyController.available_blocks.keys.include? b %>
<%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>
<% end if @blocks['top'] %>
</div>
<div id="list-left" class="splitcontentleft block-receiver">
<% @blocks['left'].each do |b|
next unless MyController::BLOCKS.keys.include? b %>
next unless MyController.available_blocks.keys.include? b %>
<%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>
<% end if @blocks['left'] %>
</div>
<div id="list-right" class="splitcontentright block-receiver">
<% @blocks['right'].each do |b|
next unless MyController::BLOCKS.keys.include? b %>
next unless MyController.available_blocks.keys.include? b %>
<%= render :partial => 'block', :locals => {:user => @user, :block_name => b} %>
<% end if @blocks['right'] %>
</div>

@ -33,9 +33,8 @@ See doc/COPYRIGHT.rdoc for more details.
'timelines.errors.report_comparison',
'timelines.filter.column.end_date',
'timelines.filter.column.name',
'timelines.filter.column.planning_element_types',
'timelines.filter.column.type',
'timelines.filter.column.project_status',
'timelines.filter.column.project_type',
'timelines.filter.column.responsible',
'timelines.filter.column.start_date',
'timelines.filter.grouping_other',
@ -89,6 +88,7 @@ See doc/COPYRIGHT.rdoc for more details.
<%= javascript_include_tag 'Bitstream_Vera_Sans_400.font.js', :plugin => 'chiliproject_timelines' %>
<%= javascript_include_tag 'timelines.js', :plugin => 'chiliproject_timelines' %>
<%= javascript_include_tag 'modal.js', :plugin => 'chiliproject_timelines' %>
<%= javascript_include_tag 'timelines_modal.js', :plugin => 'chiliproject_timelines' %>
<% @timeline_header_included = true %>
<% end %>
<% end %>

@ -0,0 +1,36 @@
<%#-- 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.
++#%>
<h2><%= t(:button_change_parent_page) %>: <%= h(@page.title) %></h2>
<%= error_messages_for 'page' %>
<%= labelled_tabular_form_for @page, :url => { :action => 'update_parent_page' } do |f| %>
<div class="box">
<p>
<%= f.select :parent_id,
"<option value=''></option>".html_safe + wiki_page_options_for_select(@parent_pages,
@page.parent),
{:label => WikiPage.human_attribute_name(:parent_title) },
{:size => "#{@parent_pages.size + 1}", :class => "parent-select"} %>
</p>
<%= javascript_tag do -%>
jQuery(function() {
var parent_select = jQuery('#wiki_page_parent_id');
parent_select.attr('size', parent_select.children().length);
}
));
<% end -%>
</div>
<%= submit_tag t(:button_save) %>
<% end %>

@ -15,10 +15,9 @@ See doc/COPYRIGHT.rdoc for more details.
<%= error_messages_for 'page' %>
<%= labelled_tabular_form_for @page, :url => { :action => 'rename' }, :as => :wiki_page do |f| %>
<div class="box">
<p><%= f.text_field :title, :required => true, :size => 100 %></p>
<p><%= f.check_box :redirect_existing_links %></p>
<p><%= f.select :parent_id, content_tag(:option, '', :value => '') + wiki_page_options_for_select(@wiki.pages.all(:include => :parent) - @page.self_and_descendants, @page.parent), :label => WikiPage.human_attribute_name(:parent_title) %></p>
</div>
<%= submit_tag l(:button_rename) %>
<div class="box">
<p><%= f.text_field :title, :required => true, :size => 100 %></p>
<p><%= f.check_box :redirect_existing_links %></p>
</div>
<%= submit_tag l(:button_rename) %>
<% end %>

@ -26,7 +26,14 @@ See doc/COPYRIGHT.rdoc for more details.
<%= content_tag(:li, link_to(l(:create_child_page), wiki_new_child_path(:id => @page.title, :project_id => @project), :class => 'icon icon-duplicate')) %>
<% end %>
<% end %>
<%= li_unless_nil(link_to_if_authorized(l(:button_rename), {:action => 'rename', :id => @page.title}, :class => 'icon icon-rename')) if @content.version == @page.content.version %>
<% if @content.version == @page.content.version %>
<%= li_unless_nil(link_to_if_authorized(t(:button_rename),
{:action => 'rename', :id => @page.title},
:class => 'icon icon-rename')) %>
<%= li_unless_nil(link_to_if_authorized(t(:button_change_parent_page),
{:action => 'edit_parent_page', :id => @page.title},
:class => 'icon icon-parent')) %>
<% end %>
<%= li_unless_nil(link_to_if_authorized(l(:button_delete), {:action => 'destroy', :id => @page.title}, :method => :delete, :confirm => l(:text_are_you_sure), :class => 'icon icon-del')) %>
<%= li_unless_nil(link_to_if_authorized(l(:button_rollback), {:action => 'edit', :id => @page.title, :version => @content.version }, :class => 'icon icon-cancel')) if @content.version < @page.content.version %>
<% end %>

@ -16,10 +16,6 @@ See doc/COPYRIGHT.rdoc for more details.
(<%= link_to(l(:label_add_subtask), new_project_work_package_path(:project_id => @project,
:sti_type => Issue.to_s,
:work_package => { :parent_id => work_package })) %>)
<% elsif controller.work_package.is_a? PlanningElement %>
(<%= link_to(l(:label_add_subtask), new_project_work_package_path(:project_id => @project,
:sti_type => PlanningElement.to_s,
:work_package => { :parent_id => work_package })) %>)
<% end %>
<% end %>

@ -88,14 +88,9 @@ de:
issue: "Ticket"
spent_on: "Datum"
type: "Typ"
planning_element_type_color:
type_color:
name: "Name"
hexcode: "Hex-Code"
planning_element:
description: "Beschreibung"
id: "ID"
name: "Name"
start_date: "Startdatum"
project_association:
description: "Begründung"
project_type:
@ -148,7 +143,6 @@ de:
progress: "% erledigt"
responsible: "Planungsverantwortlicher"
spent_time: "Aufgewendete Zeit"
errors:
messages:
accepted: "muss akzeptiert werden"
@ -183,7 +177,7 @@ de:
before: "muss vor %{date} sein"
before_or_equal_to: "muss vor oder gleich %{date} sein"
models:
planning_element:
work_package:
attributes:
due_date:
not_start_date: "ist nicht am Startdatum, obwohl die bei Meilensteinen Pflicht ist"
@ -287,6 +281,7 @@ de:
button_back: "Zurück"
button_cancel: "Abbrechen"
button_change: "Wechseln"
button_change_parent_page: "Übergeordnete Seite ändern"
button_change_password: "Kennwort ändern"
button_check_all: "Alles auswählen"
button_clear: "Zurücksetzen"
@ -1048,6 +1043,7 @@ de:
permission_add_subprojects: "Unterprojekte erstellen"
permission_add_work_package_watchers: "Beobachter hinzufügen"
permission_browse_repository: "Projektarchiv ansehen"
permission_change_wiki_parent_page: "Übergeordnete Wiki Seite ändern"
permission_comment_news: "News kommentieren"
permission_commit_access: "Commit-Zugriff (über WebDAV)"
permission_delete_work_package_watchers: "Beobachter löschen"
@ -1336,10 +1332,10 @@ de:
date: "Datum"
default: "Default"
column:
type: "Typ"
end_date: "Abschlussdatum"
name: "Name"
project_status: "Projekt-Status"
project_type: "Projekt-Typ"
responsible: "Planungsverantwortlicher"
start_date: "Startdatum"
columns: "Spalten"

@ -123,14 +123,6 @@ en:
planning_element_type_color:
name: Name
hexcode: Hex code
planning_element:
description: Description
id: ID
name: Name
start_date: Start date
deleted_at: Deleted at
planning_element_status: "Status"
# parent: Subelement of
project_association:
description: Cause
project_type:
@ -153,7 +145,6 @@ en:
progress: "% done"
responsible: "Responsible"
spent_time: "Spent time"
errors:
messages:
accepted: "must be accepted"
@ -187,16 +178,16 @@ en:
before: "must be before %{date}"
before_or_equal_to: "must be before or equal to %{date}"
models:
planning_element:
project_association:
identical_projects: "can not be created from one project to itself"
project_association_not_allowed: "does not allow associations"
work_package:
attributes:
due_date:
not_start_date: "is not on start date, although this is required for milestones"
parent:
cannot_be_milestone: "cannot be a milestone"
cannot_be_in_another_project: "cannot be in another project"
project_association:
identical_projects: "can not be created from one project to itself"
project_association_not_allowed: "does not allow associations"
user:
attributes:
password:
@ -291,6 +282,7 @@ en:
button_back: "Back"
button_cancel: "Cancel"
button_change: "Change"
button_change_parent_page: "Change parent page"
button_change_password: "Change password"
button_check_all: "Check all"
button_clear: "Clear"
@ -1027,6 +1019,7 @@ en:
permission_add_subprojects: "Create subprojects"
permission_add_work_package_watchers: "Add watchers"
permission_browse_repository: "Browse repository"
permission_change_wiki_parent_page: "Change parent wiki page"
permission_comment_news: "Comment news"
permission_commit_access: "Commit access"
permission_delete_work_package_watchers: "Delete watchers"
@ -1318,10 +1311,10 @@ en:
date: "date"
default: "default"
column:
type: "Type"
end_date: "End date"
name: "Name"
project_status: "Project status"
project_type: "Project type"
responsible: "Responsible"
start_date: "Start date"
columns: "Columns"

@ -172,6 +172,8 @@ OpenProject::Application.routes.draw do
get '/diff(/:version)' => 'wiki#diff', :as => 'wiki_diff'
get '/annotate/:version' => 'wiki#annotate', :as => 'wiki_annotate'
match :rename, :via => [:get, :put]
get :parent_page, :action => 'edit_parent_page'
put :parent_page, :action => 'update_parent_page'
get :history
post :preview
post :protect
@ -368,13 +370,14 @@ OpenProject::Application.routes.draw do
match '/projects/:id/repository/statistics', :action => :stats
match '/projects/:id/repository/committers', :action => :committers
match '/projects/:id/repository/graph', :action => :graph
match '/projects/:id/repository/diff', :action => :diff
match '/projects/:id/repository/revisions', :action => :revisions
match '/projects/:id/repository/revisions.:format', :action => :revisions
match '/projects/:id/repository/revisions/:rev', :action => :revision
match '/projects/:id/repository/revisions/:rev/diff/*path(.:format)', :action => :diff
match '/projects/:id/repository/revisions/:rev/raw/*path', :action => :entry, :format => 'raw', :rev => /[a-z0-9\.\-_]+/
match '/projects/:id/repository/revisions/:rev/raw/*path', :action => :entry, :kind => 'raw', :rev => /[a-z0-9\.\-_]+/
match '/projects/:id/repository/revisions/:rev/:action/*path', :rev => /[a-z0-9\.\-_]+/
match '/projects/:id/repository/raw/*path', :action => :entry, :format => 'raw'
match '/projects/:id/repository/raw/*path', :action => :entry, :kind => 'raw'
# TODO: why the following route is required?
match '/projects/:id/repository/entry/*path', :action => :entry
match '/projects/:id/repository/:action/*path'

@ -232,42 +232,42 @@ print "Creating objects for..."
child_element = nil
element = PlanningElement.create!(project: project,
author: user,
status: statuses.sample,
subject: Faker::Lorem.words(5).join(" "),
description: Faker::Lorem.paragraph(5, true,3),
type: types.sample,
start_date: start_date,
due_date: due_date)
rand(5).times do
print "."
sub_start_date = rand(start_date..due_date)
sub_due_date = rand(sub_start_date..due_date)
child_element = PlanningElement.create!(project: project,
parent: element,
author: user,
status: statuses.sample,
subject: Faker::Lorem.words(5).join(" "),
description: Faker::Lorem.paragraph(5, true,3),
type: types.sample,
start_date: sub_start_date,
due_date: sub_due_date)
end
[element, child_element].compact.each do |e|
2.times do
print "."
e.reload
e.status = statuses.sample if rand(99).even?
e.subject = Faker::Lorem.words(8).join(" ") if rand(99).even?
e.description = Faker::Lorem.paragraph(5, true,3) if rand(99).even?
e.type = types.sample if rand(99).even?
e.save!
end
end
# element = PlanningElement.create!(project: project,
# author: user,
# status: statuses.sample,
# subject: Faker::Lorem.words(5).join(" "),
# description: Faker::Lorem.paragraph(5, true,3),
# type: types.sample,
# start_date: start_date,
# due_date: due_date)
# rand(5).times do
# print "."
# sub_start_date = rand(start_date..due_date)
# sub_due_date = rand(sub_start_date..due_date)
# child_element = PlanningElement.create!(project: project,
# parent: element,
# author: user,
# status: statuses.sample,
# subject: Faker::Lorem.words(5).join(" "),
# description: Faker::Lorem.paragraph(5, true,3),
# type: types.sample,
# start_date: sub_start_date,
# due_date: sub_due_date)
# end
#
# [element, child_element].compact.each do |e|
# 2.times do
# print "."
# e.reload
#
# e.status = statuses.sample if rand(99).even?
# e.subject = Faker::Lorem.words(8).join(" ") if rand(99).even?
# e.description = Faker::Lorem.paragraph(5, true,3) if rand(99).even?
# e.type = types.sample if rand(99).even?
#
# e.save!
# end
# end
end

@ -1,12 +1,22 @@
# Changelog
* `#1898` Separate action for changing wiki parent page (was same as rename before)
## 3.0.0pre15
* `#1301` Ajax call when logged out should open a popup window
* `#1351` Generalize Modal Creation
# `#1557` Timeline Report Selection Not Visible
* `#1755` Migrate helper-tests for issues into specs for work package
* `#1766` Fixed bug: Viewing diff of Work Package description results in error 500
* `#1767` Fixed bug: Viewing changesets results in "page not found"
* `#1789` Move validation to Work Package
* `#1800` Add settings to change software name and URL and add additional footer content
* `#1808` Add option to log user for each request
* `#1875` Added test steps to reuse steps for my page, my project page, and documents, no my page block lookup at class load time
* `#1876` Timelines do not show work packages when there is no status reporting
* `#1896` Moved visibility-tests for issues into specs for workpackages
* `#1912` Merge column project type with column planning element type
## 3.0.0pre14

@ -20,30 +20,19 @@ Feature: Doing Ajax when logged out
And there is a role "manager"
And the role "manager" may have the following rights:
| view_timelines |
| edit_timelines |
| view_planning_elements |
| view_work_packages |
And there is a project named "ecookbook" of type "Standard Project"
And I am working in project "ecookbook"
And the project uses the following modules:
| timelines |
And the user "manager" is a "manager"
And I am logged in as "manager"
And there are the following planning elements:
| Subject |
| January |
@javascript
Scenario: If we do ajax while being logged out a confirm dialog should open
Given there is a timeline "Testline" for project "ecookbook"
When I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
When I go to the work packages index page of the project "ecookbook"
And I log out in the background
And I open a modal for planning element "January" of project "ecookbook"
And I do some ajax
And I confirm popups
Then I should be on the login page

@ -37,7 +37,7 @@ Feature: Viewing a planning_element
And the user "manager" is a "manager"
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject | start_date | due_date |
| pe1 | 2013-01-01 | 2013-12-31 |

@ -9,10 +9,13 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
Then /^the breadcrumb should contain "(.+)"$/ do |string|
container = ChiliProject::VERSION::MAJOR < 2 ? "p.breadcrumb a" : "#breadcrumb a"
steps %Q{ Then I should see "#{string}" within "#{container}" }
Then /^the breadcrumbs should (not )?have the element "(.+)"$/ do |negation, string|
# find all descendants of an element with id 'breadcrumb' that have a child text node equalling
# string
selector = have_xpath("//*[@id='breadcrumb']//*[text()='#{string}']")
if negation == 'not '
should_not selector
else
should selector
end
end

@ -87,17 +87,6 @@ Then /^the project "([^"]*)" is( not)? public$/ do |project_name, negation|
p.update_attribute(:is_public, !negation)
end
Given /^the [Pp]roject "([^\"]*)" has 1 [wW]iki(?: )?[pP]age with the following:$/ do |project, table|
p = Project.find_by_name(project)
p.wiki.create! unless p.wiki
page = FactoryGirl.create(:wiki_page, :wiki => p.wiki)
content = FactoryGirl.create(:wiki_content, :page => page)
send_table_to_object(page, table)
end
Given /^the plugin (.+) is loaded$/ do |plugin_name|
plugin_name = plugin_name.gsub("\"", "")
Redmine::Plugin.all.detect {|x| x.id == plugin_name.to_sym}.present? ? nil : pending("Plugin #{plugin_name} not loaded")

@ -0,0 +1,34 @@
#-- 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.
#++
Then /^the "(.+)" widget should be in the top block$/ do |widget_name|
steps %{Then I should see "#{widget_name}" within "#list-top"}
end
When /^I select "(.+)" from the available widgets drop down$/ do |widget_name|
steps %{When I select "#{widget_name}" from "block-select"}
end
Then /^I should see the dropdown of available widgets$/ do
page.has_select?('block-select', :options => ['Watched Issues', 'Issues assigned to me'])
end
Then(/^I should see the widget "([^"]*)"$/) do |arg|
page.find("#widget_#{arg}").should_not be_nil
end
Then /^"(.+)" should be disabled in the my page available widgets drop down$/ do |widget_name|
option_name = MyController.available_blocks.detect{|k, v| I18n.t(v) == widget_name}.first.dasherize
steps %Q{Then the "block-select" drop-down should have the following options disabled:
| #{option_name} |}
end

@ -9,14 +9,13 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
Given (/^there are the following planning elements(?: in project "([^"]*)")?:$/) do |project_name, table|
Given (/^there are the following work packages(?: in project "([^"]*)")?:$/) do |project_name, table|
project = get_project(project_name)
table.map_headers! { |header| header.underscore.gsub(' ', '_') }
table.hashes.each do |type_attributes|
[
["planning_element_status", PlanningElementStatus],
["author", User],
["responsible", User],
["assigned_to", User],
@ -39,6 +38,6 @@ Given (/^there are the following planning elements(?: in project "([^"]*)")?:$/)
type_attributes[:type] = Type.where(name: type_attributes[:type].to_s).first
end
factory = FactoryGirl.create(:planning_element, type_attributes.merge(:project_id => project.id))
factory = FactoryGirl.create(:work_package, type_attributes.merge(:project_id => project.id))
end
end

@ -10,9 +10,6 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
Then /^I should see the planning element edit modal$/ do
steps 'Then I should see a modal window with selector "#planningElementDialog"'
end
Then /^I should see a modal window with selector "(.*?)"$/ do |selector|
page.should have_selector(selector)
dialog = find(selector)
@ -20,8 +17,19 @@ Then /^I should see a modal window with selector "(.*?)"$/ do |selector|
dialog["class"].include?("ui-dialog-content").should be_true
end
Then /^I should see a modal window$/ do
steps 'Then I should see a modal window with selector ".ui-dialog-content"'
steps 'Then I should see a modal window with selector "#modalDiv"'
end
Then /^(.*) in the modal$/ do |step|
step(step + ' in the iframe "modalIframe"')
end
Then(/^I should not see the planning element "(.*?)"$/) do |planning_element_name|
steps %Q{
Then I should not see "#{planning_element_name}" within ".tl-left-main"
}
end
Then(/^the project "(.*?)" should have an indent of (\d+)$/) do |project_name, indent|
find(".tl-indent-#{indent}", :text => project_name).should_not be_nil
end
@ -152,8 +160,8 @@ Then /^I should (not )?see the timeline "([^"]*)"$/ do |negate, timeline_name|
timeline = Timeline.find_by_name(timeline_name)
if (negate && page.has_css?(selector)) || !negate
timeline.project.planning_elements.each do |planning_element|
step %Q{I should #{negate}see "#{planning_element.subject}" within "#{selector}"}
timeline.project.work_packages.each do |work_package|
step %Q{I should #{negate}see "#{work_package.subject}" within "#{selector}"}
end
end
end

@ -18,6 +18,14 @@ end
When /^I click on the Save Link$/ do
click_link("Save")
end
When(/^I switch the timeline to "(.*?)"$/) do |arg1|
within "div#s2id_timeline_select" do
click_link("Testline")
end
element = find('.select2-results', :text => 'Testline2')
element.click
element.native.send_keys(:return)
end
When(/^I hide empty projects for the timeline "([^"]*?)" of the project called "([^"]*?)"$/) do |timeline_name, project_name|
steps %Q{
When I go to the edit page of the timeline "#{timeline_name}" of the project called "#{project_name}"
@ -114,35 +122,20 @@ When(/^I set the sortation of the first level grouping criteria to explicit orde
page.execute_script("jQuery('#timeline_options_grouping_one_sort').val('1')")
page.execute_script("jQuery('#content form').submit()")
end
When /^I click on the Restore Link$/ do
page.execute_script("jQuery('.input-as-link').click()")
end
When /^I wait (\d+) seconds?$/ do |seconds|
sleep seconds.to_i
end
When /^I wait for the modal to show$/ do
page.should have_selector('#planningElementDialog')
end
When /^I wait for the modal to close$/ do
page.should have_no_selector('#planningElementDialog')
end
When (/^I set duedate to "([^"]*)"$/) do |value|
fill_in 'planning_element_due_date', :with => value
end
When /^I wait for timeline to load table$/ do
page.should have_selector('.tl-left-main')
end
When (/^I open a modal for planning element "([^"]*)" of project "([^"]*)"$/) do |planning_element_subject, project_name|
planning_element = PlanningElement.find_by_subject(planning_element_subject)
project = Project.find_by_name(project_name)
page.execute_script <<-JS
Timeline.get().modalHelper.createPlanningModal(
'show',
#{project.id},
#{planning_element.id}
);
JS
end
When (/^I move "([^"]*)" to the top$/) do |name|
cell = find(:css, "table.list td", :text => Regexp.new("^#{name}$"))

@ -103,6 +103,10 @@ When /^(?:|I )fill in the following:$/ do |fields|
end
end
When (/^I do some ajax$/) do
click_link("Apply")
end
When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
begin
select(value, :from => field)
@ -367,6 +371,13 @@ Given /^I (accept|dismiss) the alert dialog$/ do |method|
end
end
Then /^(.*) in the iframe "([^\"]+)"$/ do |step, iframe_name|
browser = page.driver.browser
browser.switch_to.frame(iframe_name)
step(step)
browser.switch_to.default_content
end
# that's capybara's old behaviour: clicking the first button that matches
When /^(?:|I )click on the first button matching "([^"]*)"$/ do |button|
first(:button, button).click

@ -9,6 +9,17 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
Given /^the [Pp]roject "([^\"]*)" has 1 [wW]iki(?: )?[pP]age with the following:$/ do |project, table|
p = Project.find_by_name(project)
p.wiki.create! unless p.wiki
page = FactoryGirl.create(:wiki_page, :wiki => p.wiki)
content = FactoryGirl.create(:wiki_content, :page => page)
send_table_to_object(page, table)
end
Given /^there are no wiki menu items$/ do
WikiMenuItem.destroy_all
end

@ -15,7 +15,6 @@ require "rack_session_access/capybara"
InstanceFinder.register(WorkPackage, Proc.new { |name| WorkPackage.find_by_subject(name) })
RouteMap.register(WorkPackage, "/work_packages")
RouteMap.register(PlanningElement, "/work_packages")
RouteMap.register(Issue, "/work_packages")
Given /^the work package "(.*?)" has the following children:$/ do |work_package_subject, table|

@ -201,6 +201,9 @@ module NavigationHelpers
when /^the My page$/
'/my/page'
when /^the My page personalization page$/
"/my/page_layout"
when /^the [mM]y account page$/
'/my/account'
@ -266,7 +269,7 @@ module NavigationHelpers
when /^the page of the planning element "([^\"]+)" of the project called "([^\"]+)"$/
planning_element_name = $1
planning_element = PlanningElement.find_by_subject(planning_element_name)
planning_element = WorkPackage.find_by_subject(planning_element_name)
"/work_packages/#{planning_element.id}"
when /^the (.+) page (?:for|of) the project called "([^\"]+)"$/

@ -33,7 +33,7 @@ Feature: View work packages in a timeline
| Name |
| Phase1 |
And there are the following planning elements:
And there are the following work packages:
| Subject | Start date | Due date | type |
| Some planning element | 2012-01-01 | 2012-01-31 | Phase1 |

@ -31,11 +31,8 @@ Feature: Timeline View Tests
And the role "manager" may have the following rights:
| view_timelines |
| edit_timelines |
| view_work_packages |
| view_planning_elements |
| move_planning_elements_to_trash |
| delete_planning_elements |
| edit_planning_elements |
| delete_planning_elements |
And there is a project named "ecookbook" of type "Standard Project"
And I am working in project "ecookbook"
@ -61,71 +58,14 @@ Feature: Timeline View Tests
@javascript
Scenario: planning element click should show modal window
When there is a timeline "Testline" for project "ecookbook"
When there is a timeline "Testline" for project "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
And I click on the Planning Element with name "January"
Then I should see the planning element edit modal
And I should see "January (*1)"
And I should see "Avocado Hall"
And I should see "01/01/2012 - 01/31/2012"
Then I should see a modal window
And I should see "#1: January" in the modal
And I should see "Avocado Hall" in the modal
And I should see "01/01/2012" in the modal
And I should see "01/31/2012" in the modal
And I should see "New timeline report"
And I should be on the page of the timeline "Testline" of the project called "ecookbook"
@javascript
Scenario: edit should open edit
When there is a timeline "Testline" for project "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
And I click on the Planning Element with name "January"
And I click on the Edit Link
Then I should see the planning element edit modal
And I should be on the page of the timeline "Testline" of the project called "ecookbook"
And I should see "Save"
And I should see "Cancel"
@javascript
Scenario: edit in modal
When there is a timeline "Testline" for project "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
And I click on the Planning Element with name "January"
And I click on the Edit Link
And I set duedate to "2012-01-30"
And I click on the Save Link
Then I should be on the page of the timeline "Testline" of the project called "ecookbook"
And I should see "01/01/2012 - 01/30/2012"
@javascript
Scenario: enter wrong date in modal
When there is a timeline "Testline" for project "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
And I click on the Planning Element with name "January"
And I click on the Edit Link
And I set duedate to "2011-01-30"
And I click on the Save Link
Then I should be on the page of the timeline "Testline" of the project called "ecookbook"
And I should see "Due date must be greater than start date"
@javascript
Scenario: trash element
When there is a timeline "Testline" for project "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
And I trash the planning element with name "January"
Then I should be on the page of the timeline "Testline" of the project called "ecookbook"
And I should not see "January"
@javascript
Scenario: trash vertical should be removed
When there is a timeline "Testline" for project "ecookbook"
And I make the planning element "January" vertical for the timeline "Testline" of the project called "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I trash the planning element with name "January"
Then I should not see the planning element "January"
And I should be on the page of the timeline "Testline" of the project called "ecookbook"

@ -40,3 +40,21 @@ Feature: Timeline View Tests
And I should see "Testline"
And I should be on the page of the timeline "Testline" of the project called "ecookbook"
@javascript
Scenario: name column width
When there is a timeline "Testline" for project "ecookbook"
And I go to the page of the timeline "Testline" of the project called "ecookbook"
And I wait for timeline to load table
Then the first table column should not take more than 25% of the space
@javascript
Scenario: switch timeline
When there is a timeline "Testline" for project "ecookbook"
And there is a timeline "Testline2" for project "ecookbook"
And I go to the page of the project called "ecookbook"
And I toggle the "Timelines" submenu
And I follow "Timeline reports"
And I switch the timeline to "Testline2"
Then I should see "New timeline report"
And I should see "Testline2"
And I should be on the page of the timeline "Testline2" of the project called "ecookbook"

@ -28,6 +28,12 @@ Feature: Timeline View Tests with reporters
| Standard Project |
| Extraordinary Project |
And there are the following status:
| name | default |
| new | true |
| in progress | false |
| closed | false |
And there is 1 user with:
| login | manager |
@ -67,8 +73,8 @@ Feature: Timeline View Tests with reporters
And the project uses the following modules:
| timelines |
And there are the following planning elements:
| Subject | Start date | Due date | description | planning_element_status | responsible | type |
And there are the following work packages:
| Subject | Start date | Due date | description | status | responsible | type |
| January | 2012-01-01 | 2012-01-31 | Aioli Grande | closed | manager | Phase1 |
| February | 2012-02-01 | 2012-02-24 | Aioli Sali | closed | manager | Phase2 |
| March | 2012-03-01 | 2012-03-30 | Sali Grande | closed | manager | Phase3 |
@ -82,8 +88,8 @@ Feature: Timeline View Tests with reporters
And the project uses the following modules:
| timelines |
And there are the following planning elements:
| Subject | Start date | Due date | description | planning_element_status | responsible |
And there are the following work packages:
| Subject | Start date | Due date | description | status | responsible |
| January13 | 2013-01-01 | 2013-01-31 | Aioli Grande | closed | manager |
| February13 | 2013-02-01 | 2013-02-24 | Aioli Sali | closed | manager |
| March13 | 2013-03-01 | 2013-03-30 | Sali Grande | closed | manager |
@ -104,8 +110,8 @@ Feature: Timeline View Tests with reporters
And the project uses the following modules:
| timelines |
And there are the following planning elements:
| Subject | Start date | Due date | description | planning_element_status | responsible |
And there are the following work packages:
| Subject | Start date | Due date | description | status | responsible |
| July | 2012-07-01 | 2013-07-31 | Aioli Grande | closed | manager |
| August | 2012-08-01 | 2013-08-31 | Aioli Sali | closed | manager |
| Septembre | 2012-09-01 | 2013-09-30 | Sali Grande | closed | manager |

@ -21,7 +21,13 @@ Feature: Timeline Wiki Macro
| Standard Project |
| Extraordinary Project |
And there is 1 user with:
And there are the following status:
| name | default |
| new | true |
| in progress | false |
| closed | false |
And there is 1 user with:
| login | manager |
And there is 1 user with:
@ -62,8 +68,8 @@ Feature: Timeline Wiki Macro
And there are the following planning element statuses:
| Name |
| closed |
And there are the following planning elements:
| Subject | Start date | Due date | description | planning_element_status | responsible |
And there are the following work packages:
| Subject | Start date | Due date | description | status | responsible |
| January | 2012-01-01 | 2012-01-31 | Avocado Grande | closed | manager |
| February | 2012-02-01 | 2012-02-24 | Avocado Sali | closed | manager |
| March | 2012-03-01 | 2012-03-30 | Sali Grande | closed | manager |

@ -0,0 +1,39 @@
#-- 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.
#++
Feature: Parent wiki page
Background:
Given there is 1 project with the following:
| Name | Test |
And the project "Test" has 1 wiki page with the following:
| Title | Test_page |
Given the project "Test" has 1 wiki page with the following:
| Title | Parent_page |
And I am already admin
@javascript
Scenario: Changing parent page for wiki page
When I go to the wiki page "Test page" for the project called "Test"
And I click on "More functions"
And I follow "Change parent page"
When I select "Parent page" from "Parent page"
And I press "Save"
Then I should be on the wiki page "Test_page" for the project called "Test"
And the breadcrumbs should have the element "Parent page"
# no check removing the parent
When I go to the wiki page "Test page" for the project called "Test"
And I click on "More functions"
And I follow "Change parent page"
And I select "" from "Parent page"
And I press "Save"
Then I should be on the wiki page "Test_page" for the project called "Test"
And the breadcrumbs should not have the element "Parent page"

@ -26,7 +26,7 @@ Feature: A work packages changesets are displayed on the work package show page
And the user "manager" is a "manager"
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject | start_date | due_date |
| pe1 | 2013-01-01 | 2013-12-31 |

@ -42,7 +42,7 @@ Feature: Fields editable on work package edit
| name | version1 |
And the following types are enabled for projects of type "Standard Project"
| Phase |
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject | description | start_date | due_date | done_ratio | type | responsible | assigned_to | priority | parent | estimated_hours | fixed_version |
| parentpe | | | | 0 | Phase | | | prio1 | | | |
| pe1 | pe1 description | 2013-01-01 | 2013-12-31 | 30 | Phase | manager | manager | prio1 | parentpe | 5 | version1 |
@ -72,7 +72,7 @@ Feature: Fields editable on work package edit
| view_work_packages |
| log_time |
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject |
| pe1 |
@ -96,7 +96,7 @@ Feature: Fields editable on work package edit
| name | type |
| cf1 | int |
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject |
| pe1 |

@ -27,7 +27,7 @@ Feature: Error messages are displayed
| log_time |
And I am working in project "ecookbook"
And the user "manager" is a "manager"
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject |
| pe1 |
And there is an activity "design"

@ -29,7 +29,7 @@ Feature: Logging time on work package update
And there are the following status:
| name | default |
| status1 | true |
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject |
| pe1 |
And there is an activity "design"

@ -61,7 +61,7 @@ Feature: Copying a work package
| issue1 | Bug |
| issue2 | Bug |
And there are the following planning elements in project "project_1":
And there are the following work packages in project "project_1":
| subject | start_date | due_date | type |
| pe1 | 2013-01-01 | 2013-12-31 | Bug |
| pe2 | 2013-01-01 | 2013-12-31 | Bug |
@ -70,7 +70,7 @@ Feature: Copying a work package
| subject | type |
| issue3 | Feature |
And there are the following planning elements in project "project_2":
And there are the following work packages in project "project_2":
| subject | type |
| pe3 | Feature |

@ -16,7 +16,7 @@ Feature: Navigating to the work package edit page
And the user "manager" is a "manager"
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject |
| pe1 |

@ -42,7 +42,7 @@ Feature: Switching types of work packages
@javascript
Scenario: Previewing changes on an existing work package
Given there are the following planning elements in project "project1":
Given there are the following work packages in project "project1":
| subject | description |
| pe1 | pe1 description |
When I am on the edit page of the work package called "pe1"

@ -47,7 +47,7 @@ Feature: Updating work packages
| status2 | |
And the type "Phase1" has the default workflow for the role "manager"
And the type "Phase2" has the default workflow for the role "manager"
And there are the following planning elements in project "ecookbook":
And there are the following work packages in project "ecookbook":
| subject | type | status |
| pe1 | Phase1 | status1 |
| pe2 | | |

@ -54,7 +54,7 @@ Feature: Viewing a work package
| issue2 | Bug |
| issue3 | Bug |
And there are the following planning elements in project "omicronpersei8":
And there are the following work packages in project "omicronpersei8":
| subject | start_date | due_date |
| pe1 | 2013-01-01 | 2013-12-31 |
| pe2 | 2013-01-01 | 2013-12-31 |
@ -72,35 +72,18 @@ Feature: Viewing a work package
Then I should see "Bug #1: issue1"
Then I should see "Bug #2: issue2" within ".idnt-1"
Scenario: Call the work package page for a planning element and view the planning element
When I go to the page of the planning element "pe1" of the project called "omicronpersei8"
Then I should see "#4: pe1"
Then I should see "#5: pe2" within ".idnt-1"
Scenario: View child work package of type issue
When I go to the page of the work package "issue1"
When I click on "Bug #2" within ".idnt-1"
Then I should see "Bug #2: issue2"
Then I should see "Bug #1: issue1" within ".work-package-1"
Scenario: View child work package of type planning element
When I go to the page of the work package "pe1"
When I click on "#5" within ".idnt-1"
Then I should see "#5: pe2"
Then I should see "#4: pe1" within ".work-package-4"
Scenario: Add subtask leads to issue creation page for a parent issue
When I go to the page of the work package "issue1"
Then I should see "Add subtask"
When I click on "Add subtask"
Then I should be on the new work_package page of the project called "omicronpersei8"
Scenario: Add subtask leads to planning element creation page for a parent planning element
When I go to the page of the work package "pe1"
Then I should see "Add subtask"
When I click on "Add subtask"
Then I should be on the new work_package page of the project called "omicronpersei8"
@javascript
Scenario: Adding a relation will add it to the list of related work packages through AJAX instantly
When I go to the page of the work package "issue1"
@ -128,13 +111,6 @@ Feature: Viewing a work package
When I click "Watch" within "#content > .action_menu_main"
Then I should see "Unwatch" within "#content > .action_menu_main"
@javascript
Scenario: User adds herself as watcher to a planning element
When I go to the page of the work package "pe1"
Then I should see "Watch" within "#content > .action_menu_main"
When I click "Watch" within "#content > .action_menu_main"
Then I should see "Unwatch" within "#content > .action_menu_main"
@javascript
Scenario: User removes herself as watcher from an issue
Given user is already watching "issue1"
@ -143,14 +119,6 @@ Feature: Viewing a work package
When I click "Unwatch" within "#content > .action_menu_main"
Then I should see "Watch" within "#content > .action_menu_main"
@javascript
Scenario: User removes herself as watcher from a planning element
Given user is already watching "pe1"
When I go to the page of the work package "pe1"
Then I should see "Unwatch" within "#content > .action_menu_main"
When I click "Unwatch" within "#content > .action_menu_main"
Then I should see "Watch" within "#content > .action_menu_main"
@javascript
Scenario: Log time leads to time entry creation page for issues
When I go to the page of the work package "issue1"
@ -158,13 +126,6 @@ Feature: Viewing a work package
Then I should see "Spent time"
@javascript
Scenario: Log time leads to time entry creation page for planning element
When I go to the page of the work package "pe1"
When I select "Log time" from the action menu
Then I should see "Spent time"
@javascript
Scenario: For an issue copy leads to work package copy page
When I go to the page of the work package "issue1"
@ -172,13 +133,6 @@ Feature: Viewing a work package
Then I should see "Copy"
@javascript
Scenario: For a planning element copy leads to work package copy page
When I go to the page of the work package "pe1"
When I select "Copy" from the action menu
Then I should see "Copy"
@javascript
Scenario: For an issue move leads to work package copy page
When I go to the page of the work package "issue1"
@ -186,13 +140,6 @@ Feature: Viewing a work package
Then I should see "Move"
@javascript
Scenario: For a planning element move leads to work package copy page
When I go to the page of the work package "pe1"
When I select "Move" from the action menu
Then I should see "Move"
@javascript
Scenario: For an issue deletion leads to the work package list
When I go to the page of the work package "issue1"
@ -200,11 +147,3 @@ Feature: Viewing a work package
And I confirm popups
Then I should see "Work packages"
@javascript
Scenario: For a planning element deletion leads to the work package list
When I go to the page of the work package "pe1"
When I select "Delete" from the action menu
And I confirm popups
Then I should see "Work packages"

@ -38,7 +38,7 @@ Feature: Work package textile quickinfo links
| name | is_closed | is_default |
| New | false | true |
| In Progress | false | false |
And there are the following planning elements:
And there are the following work packages:
| Subject | Type | Start date | Due date | description | status | responsible | assigned_to |
| January | None | 2012-01-01 | 2012-01-31 | Avocado Sali Grande Grande | New | manager | manager |

@ -9,17 +9,18 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
module NestedAttributesForApi
module AssociationsMapper
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def accepts_nested_attributes_for_apis_for(*association_names)
# this allows you to set project=4711. which then dos a lookup
def map_associations_for(*association_names)
association_names.each do |association_name|
if reflection = reflect_on_association(association_name)
class_eval %Q{
def #{association_name}_with_accepts_nested_attributes_for_apis=(new_value)
def #{association_name}_with_map_associations_for=(new_value)
if new_value.present? && new_value.is_a?(Hash) && new_value.has_key?(:id)
obj = #{reflection.klass.name}.find_by_id(new_value[:id])
@ -30,12 +31,12 @@ module NestedAttributesForApi
@errors_in_nested_attributes[:#{association_name}] = [:invalid]
end
else
self.#{association_name}_without_accepts_nested_attributes_for_apis = new_value
self.#{association_name}_without_map_associations_for = new_value
end
end
alias_method_chain :#{association_name}=, :accepts_nested_attributes_for_apis
alias_method_chain :#{association_name}=, :map_associations_for
attr_accessible :#{association_name} unless included_modules.include?(ActiveModel::ForbiddenAttributesProtection)
}, __FILE__, __LINE__
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"

@ -32,7 +32,7 @@ module OpenProject
#
# 2.0.0debian-2
def self.special
'pre14'
'pre15'
end
def self.revision

@ -128,6 +128,8 @@ Redmine::AccessControl.map do |map|
map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
map.permission :manage_wiki_menu, {:wiki_menu_items => [:edit, :update]}, :require => :member
map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
map.permission :change_wiki_parent_page, {:wiki => [:edit_parent_page, :update_parent_page]},
:require => :member
map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
map.permission :view_wiki_pages, :wiki => [:index, :show, :special, :date_index]
map.permission :export_wiki_pages, :wiki => [:export]

@ -22,16 +22,17 @@ module Redmine
gem_name = plugin_id.to_s.gsub('openproject_','openproject-') if plugin_id.to_s.starts_with?('openproject_')
gem_spec = Gem.loaded_specs[gem_name]
if gem_spec.nil?
ActiveSupport::Deprecation.warn "No Gemspec found for plugin: " + plugin_id
+ ", expected gem name to match the plugin name but starting with openproject-"
error = "No Gemspec found for plugin: " + plugin_id.to_s \
+ ", expected gem name to match the plugin name but starting with openproject-"
ActiveSupport::Deprecation.warn(error)
nil
else
gem_spec.full_gem_path + '/**/my/blocks/_*.{rhtml,erb}'
gem_spec.full_gem_path + '/app/views/my/blocks/_*.{rhtml,erb}'
end
end.compact
).inject({}) do |h,file|
name = File.basename(file).split('.').first.gsub(/^_/, '')
h[name] = name.to_sym
h[name] = ("label_" + name).to_sym
h
end
end

@ -16,7 +16,7 @@ describe Api::V2::PlanningElementJournalsController do
describe 'index.xml' do
def fetch
planning_element = FactoryGirl.create(:planning_element,
planning_element = FactoryGirl.create(:work_package,
:project_id => project.id)
get 'index', :project_id => project.identifier,

@ -138,9 +138,9 @@ describe Api::V2::PlanningElementsController do
describe 'w/ 3 planning elements within the project' do
before do
@created_planning_elements = [
FactoryGirl.create(:planning_element, :project_id => project.id),
FactoryGirl.create(:planning_element, :project_id => project.id),
FactoryGirl.create(:planning_element, :project_id => project.id)
FactoryGirl.create(:work_package, :project_id => project.id),
FactoryGirl.create(:work_package, :project_id => project.id),
FactoryGirl.create(:work_package, :project_id => project.id)
]
end
@ -205,13 +205,13 @@ describe Api::V2::PlanningElementsController do
describe 'w/ 1 planning element in project_a and 2 in project_b' do
before do
@created_planning_elements = [
FactoryGirl.create(:planning_element, :project_id => project_a.id),
FactoryGirl.create(:planning_element, :project_id => project_b.id),
FactoryGirl.create(:planning_element, :project_id => project_b.id)
FactoryGirl.create(:work_package, :project_id => project_a.id),
FactoryGirl.create(:work_package, :project_id => project_b.id),
FactoryGirl.create(:work_package, :project_id => project_b.id)
]
# adding another planning element, just to make sure, that the
# result set is properly filtered
FactoryGirl.create(:planning_element, :project_id => project_c.id)
FactoryGirl.create(:work_package, :project_id => project_c.id)
end
it 'assigns a planning_elements array containing all three elements' do
@ -237,7 +237,7 @@ describe Api::V2::PlanningElementsController do
def fetch
post 'create', :project_id => project.identifier,
:format => 'xml',
:planning_element => FactoryGirl.build(:planning_element,
:planning_element => FactoryGirl.build(:work_package,
:author => author,
:project_id => project.id).attributes
.merge("planning_element_type_id" => project.types.first.id)
@ -303,7 +303,7 @@ describe Api::V2::PlanningElementsController do
become_admin
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project') }
let(:planning_element) { FactoryGirl.create(:planning_element, :project_id => project.id) }
let(:planning_element) { FactoryGirl.create(:work_package, :project_id => project.id) }
describe 'w/o a given project' do
it 'renders a 404 Not Found page' do
@ -347,7 +347,7 @@ describe Api::V2::PlanningElementsController do
it 'needs to be tested'
let(:project) { FactoryGirl.create(:project, :is_public => false) }
let(:planning_element) { FactoryGirl.create(:planning_element,
let(:planning_element) { FactoryGirl.create(:work_package,
:project_id => project.id) }
def fetch
@ -410,7 +410,7 @@ describe Api::V2::PlanningElementsController do
describe 'w/ a valid planning element id' do
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project') }
let(:planning_element) { FactoryGirl.create(:planning_element, :project_id => project.id) }
let(:planning_element) { FactoryGirl.create(:work_package, :project_id => project.id) }
describe 'w/o a given project' do
it 'renders a 404 Not Found page' do

@ -23,7 +23,7 @@ describe WorkPackages::MovesController do
let(:project) { FactoryGirl.create(:project,
:is_public => false,
:types => [type, type_2]) }
let(:work_package) { FactoryGirl.create(:planning_element,
let(:work_package) { FactoryGirl.create(:work_package,
:project_id => project.id,
:type => type,
:author => user,
@ -92,7 +92,7 @@ describe WorkPackages::MovesController do
become_member_with_move_work_package_permissions
let(:target_project) { FactoryGirl.create(:project, :is_public => false) }
let(:work_package_2) { FactoryGirl.create(:planning_element,
let(:work_package_2) { FactoryGirl.create(:work_package,
:project_id => project.id,
:type => type_2,
:priority => priority) }

@ -19,9 +19,9 @@ describe WorkPackagesController do
UserMailer.stub!(:new).and_return(double('mailer').as_null_object)
end
let(:planning_element) { FactoryGirl.create(:planning_element, :project_id => project.id) }
let(:planning_element) { FactoryGirl.create(:work_package, :project_id => project.id) }
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project', :is_public => false) }
let(:stub_planning_element) { FactoryGirl.build_stubbed(:planning_element, :project_id => stub_project.id) }
let(:stub_planning_element) { FactoryGirl.build_stubbed(:work_package, :project_id => stub_project.id) }
let(:stub_project) { FactoryGirl.build_stubbed(:project, :identifier => 'test_project', :is_public => false) }
let(:stub_issue) { FactoryGirl.build_stubbed(:issue, :project_id => stub_project.id) }
let(:stub_user) { FactoryGirl.build_stubbed(:user) }
@ -516,34 +516,6 @@ describe WorkPackagesController do
Project.stub(:find_visible).and_return stub_project
end
describe 'when the type is "PlanningElement"' do
before do
controller.params = { :sti_type => 'PlanningElement',
:work_package => {} }.merge(params)
controller.stub(:current_user).and_return(stub_user)
controller.send(:permitted_params).should_receive(:new_work_package)
.with(:project => stub_project)
.and_return(wp_params)
stub_project.should_receive(:add_planning_element) do |args|
expect(args[:author]).to eql stub_user
end.and_return(stub_planning_element)
end
it 'should return a new planning element on the project' do
controller.work_package.should == stub_planning_element
end
it 'should copy over attributes from another work_package provided as the source' do
controller.params[:copy_from] = 2
stub_planning_element.should_receive(:copy_from).with(2, :exclude => [:project_id])
controller.work_package
end
end
describe 'when the type is "Issue"' do
before do
@ -704,7 +676,7 @@ describe WorkPackagesController do
end
describe "when work_package is a planning element" do
let(:descendant_planning_element) { FactoryGirl.create(:planning_element, :project => project,
let(:descendant_planning_element) { FactoryGirl.create(:work_package, :project => project,
:parent_id => planning_element.id) }
it "should return the work_packages ancestors" do
controller.stub!(:work_package).and_return(descendant_planning_element)

@ -1,56 +0,0 @@
#encoding: utf-8
#-- 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(:planning_element, :class => PlanningElement) do
priority
status :factory => :issue_status
prepared_names = [
"Backup",
"BzA",
"Design",
"Detailed Design",
"Development",
"Feasability Study",
"Impact Analysis",
"Integrationstest",
"New Testing",
"PNPA",
"Preproduction",
"Realization",
"Reporting",
"Rollout",
"Specification",
"Superelement",
"Test",
"Testdurchführung",
"Testing",
"Testplanung",
"Testplanung",
"Testspezifikation"
]
sequence(:subject) { |n| "#{prepared_names.sample} No. #{n}" }
sequence(:description) { |n| "Planning Element No. #{n} is the most important part of the project." }
sequence(:start_date) { |n| ((n - 1) * 7).days.since.to_date }
sequence(:due_date) { |n| (n * 7).days.since.to_date }
association :author, :factory => :user
association :project, :factory => :project_with_types
after :build do |planning_element|
planning_element.type = planning_element.project.types.first unless planning_element.type
end
end
end

@ -12,7 +12,7 @@
require 'spec_helper'
describe WorkPackagesHelper do
let(:stub_work_package) { FactoryGirl.build_stubbed(:planning_element) }
let(:stub_work_package) { FactoryGirl.build_stubbed(:work_package) }
let(:stub_project) { FactoryGirl.build_stubbed(:project) }
let(:stub_type) { FactoryGirl.build_stubbed(:type) }
let(:form) { double('form', :select => "").as_null_object }
@ -239,7 +239,7 @@ describe WorkPackagesHelper do
let(:statuses) { (1..5).map{ |i| FactoryGirl.build_stubbed(:issue_status)}}
let(:priority) { FactoryGirl.build_stubbed :priority, is_default: true }
let(:status) { statuses[0] }
let(:stub_work_package) { FactoryGirl.build_stubbed(:planning_element,
let(:stub_work_package) { FactoryGirl.build_stubbed(:work_package,
:status => status,
:priority => priority) }

@ -142,88 +142,6 @@ describe PermittedParams do
end
end
describe :planning_element do
it "should permit planning_element" do
hash = { "subject" => "blubs" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit description" do
hash = { "description" => "blubs" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit start_date" do
hash = { "start_date" => "2012-12-12" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit due_date" do
hash = { "due_date" => "2012-12-12" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit note" do
hash = { "note" => "lorem" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit planning_element_type_id" do
hash = { "planning_element_type_id" => "1" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit planning_element_status_comment" do
hash = { "planning_element_status_comment" => "lorem" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit planning_element_status_id" do
hash = { "planning_element_status_id" => "1" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit parent_id" do
hash = { "parent_id" => "1" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
it "should permit responsible_id" do
hash = { "responsible_id" => "1" }
params = ActionController::Parameters.new(:planning_element => hash)
PermittedParams.new(params, user).planning_element.should == hash
end
end
describe :new_work_package do
it "should permit subject" do

@ -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.
#++
require File.expand_path('../../spec_helper', __FILE__)
describe PlanningElementStatus do
describe '- Relations ' do
describe '#planning_elements' do
it 'can read planning_elements w/ the help of the has_many association' do
planning_element_status = FactoryGirl.create(:planning_element_status)
planning_element = FactoryGirl.create(:planning_element,
:planning_element_status_id => planning_element_status.id)
planning_element_status.reload
planning_element_status.planning_elements.size.should == 1
planning_element_status.planning_elements.first.should == planning_element
end
end
end
end

@ -58,34 +58,6 @@ describe Project do
end
end
describe "add_planning_element" do
let(:project) { FactoryGirl.create(:project_with_types) }
it 'should return a work package' do
project.add_planning_element.should be_a(PlanningElement)
end
it 'the object should be a new record' do
project.add_planning_element.should be_new_record
end
it 'should have the project associated' do
project.add_planning_element.project.should == project
end
it 'should assign the attributes' do
attributes = { :blubs => double('blubs') }
new_pe = FactoryGirl.build_stubbed(:planning_element)
project.planning_elements.should_receive(:build).and_yield(new_pe)
new_pe.should_receive(:attributes=).with(attributes)
project.add_planning_element(attributes)
end
end
describe "add_issue" do
let(:project) { FactoryGirl.create(:project_with_types) }

@ -86,26 +86,6 @@ describe Project do
end
end
describe '#planning_elements' do
it 'can read planning elements w/ the help of the has_many association' do
project = FactoryGirl.create(:project)
planning_element = FactoryGirl.create(:planning_element, :project_id => project.id)
project.reload
project.planning_elements.size.should == 1
project.planning_elements.first.should == planning_element
end
it 'deletes associated planning elements' do
planning_element = FactoryGirl.create(:planning_element)
planning_element.project.destroy
expect { planning_element.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe '#reportings' do
it 'can read reportings via source w/ the help of the has_many association' do
project = FactoryGirl.create(:project)

@ -44,7 +44,7 @@ describe WorkPackage do
end
context "on work package change" do
let(:parent_work_package) { FactoryGirl.create(:planning_element,
let(:parent_work_package) { FactoryGirl.create(:work_package,
:project_id => project.id,
:type => type,
:priority => priority) }

@ -18,11 +18,11 @@ describe WorkPackage do
let(:issue) { FactoryGirl.build(:issue, :project => project, :type => project.types.first) }
let(:issue2) { FactoryGirl.build(:issue, :project => project, :type => project.types.first) }
let(:issue3) { FactoryGirl.build(:issue, :project => project, :type => project.types.first) }
let(:planning_element) { FactoryGirl.build(:planning_element, :project => project) }
let(:planning_element2) { FactoryGirl.build(:planning_element, :project => project) }
let(:planning_element3) { FactoryGirl.build(:planning_element, :project => project) }
let(:work_package) { FactoryGirl.build(:work_package, :project => project) }
let(:work_package2) { FactoryGirl.build(:work_package, :project => project) }
let(:work_package3) { FactoryGirl.build(:work_package, :project => project) }
[:issue, :planning_element].each do |subclass|
[:issue, :work_package].each do |subclass|
describe "(#{subclass})" do
let(:instance) { send(subclass) }

@ -11,7 +11,7 @@
require File.expand_path('../../spec_helper', __FILE__)
describe PlanningElement do
describe WorkPackage do
let(:project) { FactoryGirl.create(:project_with_types) }
let(:user) { FactoryGirl.create(:user) }
@ -24,7 +24,7 @@ describe PlanningElement do
describe '#project' do
it 'can read the project w/ the help of the belongs_to association' do
project = FactoryGirl.create(:project)
planning_element = FactoryGirl.create(:planning_element,
planning_element = FactoryGirl.create(:work_package,
:project_id => project.id)
planning_element.reload
@ -34,7 +34,7 @@ describe PlanningElement do
it 'can read the responsible w/ the help of the belongs_to association' do
user = FactoryGirl.create(:user)
planning_element = FactoryGirl.create(:planning_element,
planning_element = FactoryGirl.create(:work_package,
:responsible_id => user.id)
planning_element.reload
@ -44,7 +44,7 @@ describe PlanningElement do
it 'can read the type w/ the help of the belongs_to association' do
type = project.types.first
planning_element = FactoryGirl.create(:planning_element,
planning_element = FactoryGirl.create(:work_package,
:type_id => type.id,
:project => project)
@ -54,20 +54,20 @@ describe PlanningElement do
end
it 'can read the planning_element_status w/ the help of the belongs_to association' do
planning_element_status = FactoryGirl.create(:planning_element_status)
planning_element = FactoryGirl.create(:planning_element,
:planning_element_status_id => planning_element_status.id)
status = FactoryGirl.create(:issue_status)
work_package = FactoryGirl.create(:work_package,
:status_id => status.id)
planning_element.reload
work_package.reload
planning_element.planning_element_status.should == planning_element_status
work_package.status.should == status
end
end
end
describe '- Validations ' do
let(:attributes) {
{:subject => 'Planning Element No. 1',
{:subject => 'workpackage No. 1',
:start_date => Date.today,
:due_date => Date.today + 2.weeks,
:project_id => project.id,
@ -76,12 +76,12 @@ describe PlanningElement do
}
}
it { PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }.should be_valid }
it { WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }.should be_valid }
describe 'subject' do
it 'is invalid w/o a subject' do
attributes[:subject] = nil
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should_not be_valid
@ -91,7 +91,7 @@ describe PlanningElement do
it 'is invalid w/ a subject longer than 255 characters' do
attributes[:subject] = "A" * 500
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should_not be_valid
@ -103,7 +103,7 @@ describe PlanningElement do
describe 'start_date' do
it 'is valid w/o a start_date' do
attributes[:start_date] = nil
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should be_valid
@ -114,7 +114,7 @@ describe PlanningElement do
describe 'due_date' do
it 'is valid w/o a due_date' do
attributes[:due_date] = nil
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should be_valid
@ -124,7 +124,7 @@ describe PlanningElement do
it 'is invalid if start_date is after due_date' do
attributes[:start_date] = Date.today
attributes[:due_date] = Date.today - 1.week
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should_not be_valid
@ -136,7 +136,7 @@ describe PlanningElement do
attributes[:type] = FactoryGirl.build(:type, :is_milestone => true)
attributes[:start_date] = Date.today
attributes[:due_date] = Date.today + 1.week
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should_not be_valid
@ -148,7 +148,7 @@ describe PlanningElement do
describe 'project' do
it 'is invalid w/o a project' do
attributes[:project_id] = nil
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should_not be_valid
@ -158,27 +158,41 @@ describe PlanningElement do
end
describe 'parent' do
let (:de_message){ "darf kein Meilenstein sein"}
let (:en_message){ "cannot be a milestone"}
after(:each) do
#proper reset of the locale after the test
I18n.locale = "en"
end
it 'is invalid if parent is_milestone' do
parent = PlanningElement.new.tap do |pe|
pe.send(:assign_attributes, attributes.merge(:type => FactoryGirl.build(:type, :is_milestone => true)), :without_protection => true)
end
["en","de"].each do |locale|
I18n.with_locale(locale) do
parent = WorkPackage.new.tap do |pe|
pe.send(:assign_attributes, attributes.merge(:type => FactoryGirl.build(:type, :is_milestone => true)), :without_protection => true)
end
attributes[:parent] = parent
planning_element = PlanningElement.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
attributes[:parent] = parent
planning_element = WorkPackage.new.tap { |pe| pe.send(:assign_attributes, attributes, :without_protection => true) }
planning_element.should_not be_valid
planning_element.errors[:parent].should be_present
planning_element.errors[:parent].should == [self.send("#{I18n.locale}_message")]
end
end
planning_element.should_not be_valid
planning_element.errors[:parent].should be_present
planning_element.errors[:parent].should == ["cannot be a milestone"]
end
end
end
describe 'derived attributes' do
before do
@pe1 = FactoryGirl.create(:planning_element, :project_id => project.id)
@pe11 = FactoryGirl.create(:planning_element, :project_id => project.id, :parent_id => @pe1.id)
@pe12 = FactoryGirl.create(:planning_element, :project_id => project.id, :parent_id => @pe1.id)
@pe1 = FactoryGirl.create(:work_package, :project_id => project.id)
@pe11 = FactoryGirl.create(:work_package, :project_id => project.id, :parent_id => @pe1.id)
@pe12 = FactoryGirl.create(:work_package, :project_id => project.id, :parent_id => @pe1.id)
end
describe 'start_date' do
@ -211,9 +225,9 @@ describe PlanningElement do
let(:type) { project.types.first } # The type-validation, that now lives on work-package is more
# strict than the previous validation on the planning-element
# it also checks, that the type is available for the project the pe lives in.
let(:pe_status) { FactoryGirl.create(:planning_element_status) }
let(:pe_status) { FactoryGirl.create(:issue_status) }
let(:pe) { FactoryGirl.create(:planning_element,
let(:pe) { FactoryGirl.create(:work_package,
:subject => "Plan A",
:author => responsible,
:description => "This won't work out",
@ -222,8 +236,7 @@ describe PlanningElement do
:project_id => project.id,
:responsible_id => responsible.id,
:type_id => type.id,
:planning_element_status_id => pe_status.id,
:planning_element_status_comment => 'All lost'
:status_id => pe_status.id
) }
it "has an initial journal, so that it's creation shows up in activity" do
@ -231,7 +244,7 @@ describe PlanningElement do
changes = pe.journals.first.changed_data.to_hash
changes.size.should == 13
changes.size.should == 11
changes.should include(:subject)
changes.should include(:author_id)
@ -244,8 +257,6 @@ describe PlanningElement do
changes.should include(:project_id)
changes.should include(:responsible_id)
changes.should include(:type_id)
changes.should include(:planning_element_status_id)
changes.should include(:planning_element_status_comment)
end
it 'stores updates in journals' do
@ -263,8 +274,8 @@ describe PlanningElement do
changes[:due_date].last.should == Date.new(2012, 2, 1)
end
describe 'planning element hierarchies' do
let(:child_pe) { FactoryGirl.create(:planning_element,
describe 'workpackage hierarchies' do
let(:child_pe) { FactoryGirl.create(:work_package,
:parent_id => pe.id,
:subject => "Plan B",
:description => "This will work out",
@ -302,7 +313,7 @@ describe PlanningElement do
describe 'acts as paranoid trash' do
before(:each) do
@pe1 = FactoryGirl.create(:planning_element,
@pe1 = FactoryGirl.create(:work_package,
:project_id => project.id,
:start_date => Date.new(2011, 1, 1),
:due_date => Date.new(2011, 2, 1),
@ -312,25 +323,25 @@ describe PlanningElement do
it 'should delete the object permanantly when using destroy' do
@pe1.destroy
PlanningElement.without_deleted.find_by_id(@pe1.id).should be_nil
PlanningElement.find_by_id(@pe1.id).should be_nil
WorkPackage.without_deleted.find_by_id(@pe1.id).should be_nil
WorkPackage.find_by_id(@pe1.id).should be_nil
end
it 'destroys all child elements' do
pe1 = FactoryGirl.create(:planning_element, :project_id => project.id)
pe11 = FactoryGirl.create(:planning_element, :project_id => project.id, :parent_id => pe1.id)
pe12 = FactoryGirl.create(:planning_element, :project_id => project.id, :parent_id => pe1.id)
pe121 = FactoryGirl.create(:planning_element, :project_id => project.id, :parent_id => pe12.id)
pe2 = FactoryGirl.create(:planning_element, :project_id => project.id)
pe1 = FactoryGirl.create(:work_package, :project_id => project.id)
pe11 = FactoryGirl.create(:work_package, :project_id => project.id, :parent_id => pe1.id)
pe12 = FactoryGirl.create(:work_package, :project_id => project.id, :parent_id => pe1.id)
pe121 = FactoryGirl.create(:work_package, :project_id => project.id, :parent_id => pe12.id)
pe2 = FactoryGirl.create(:work_package, :project_id => project.id)
pe1.destroy
[pe1, pe11, pe12, pe121].each do |pe|
PlanningElement.without_deleted.find_by_id(pe.id).should be_nil
PlanningElement.find_by_id(pe.id).should be_nil
WorkPackage.without_deleted.find_by_id(pe.id).should be_nil
WorkPackage.find_by_id(pe.id).should be_nil
end
PlanningElement.without_deleted.find_by_id(pe2.id).should == pe2
WorkPackage.without_deleted.find_by_id(pe2.id).should == pe2
end
end
end

@ -18,9 +18,9 @@ describe WorkPackage, "#reschedule_after" do
let(:issue) { FactoryGirl.create(:issue, :project => project, :type => project.types.first) }
let(:issue2) { FactoryGirl.create(:issue, :project => project, :type => project.types.first) }
let(:issue3) { FactoryGirl.create(:issue, :project => project, :type => project.types.first) }
let(:planning_element) { FactoryGirl.create(:planning_element, :project => project) }
let(:planning_element2) { FactoryGirl.create(:planning_element, :project => project) }
let(:planning_element3) { FactoryGirl.create(:planning_element, :project => project) }
let(:planning_element) { FactoryGirl.create(:work_package, :project => project) }
let(:planning_element2) { FactoryGirl.create(:work_package, :project => project) }
let(:planning_element3) { FactoryGirl.create(:work_package, :project => project) }
[:issue, :planning_element].each do |subclass|

@ -17,7 +17,6 @@ describe WorkPackage do
let(:stub_version) { FactoryGirl.build_stubbed(:version) }
let(:stub_project) { FactoryGirl.build_stubbed(:project) }
let(:issue) { FactoryGirl.create(:issue) }
let(:planning_element) { FactoryGirl.create(:planning_element).reload }
let(:user) { FactoryGirl.create(:user) }
describe :assignable_users do
@ -209,7 +208,7 @@ describe WorkPackage do
describe :update_by! do
#TODO remove once only WP exists
[:issue, :planning_element].each do |subclass|
[:issue].each do |subclass|
describe "for #{subclass}" do
let(:instance) { send(subclass) }
@ -332,7 +331,7 @@ describe WorkPackage do
describe :duration do
#TODO remove once only WP exists
[:issue, :planning_element].each do |subclass|
[:issue].each do |subclass|
describe "for #{subclass}" do
let(:instance) { send(subclass) }

@ -0,0 +1,27 @@
#-- 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 WorkPackage do
describe '- Relations ' do
describe '#workpackage status' do
it 'can read planning_elements w/ the help of the has_many association' do
status = FactoryGirl.create(:issue_status)
work_package = FactoryGirl.create(:work_package,
:status_id => status.id)
WorkPackage.where(status_id: status.id).count.should == 1
WorkPackage.where(status_id: status.id).first.should == work_package
end
end
end
end

@ -31,30 +31,33 @@ describe RepositoriesController do
it { get("/projects/testproject/repository/revisions/2457").should route_to( :controller => 'repositories', :action => 'revision', :id => 'testproject', :rev => '2457')}
end
pending describe "diff" do
it { get("/projects/testproject/repository/revisions/2457/diff").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :rev => '2457')}
it { get("/projects/testproject/repository/revisions/2457/diff.diff").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :rev => '2457', :format => 'diff')}
it { get("/projects/testproject/repository/diff/path/to/file.c").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :path => %w[path to file.c])}
it { get("/projects/testproject/repository/revisions/2/diff/path/to/file.c").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :path => %w[path to file.c], :rev => '2')}
describe "diff" do
pending describe "unknown diff roots" do
it { get("/projects/testproject/repository/revisions/2457/diff").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :rev => '2457')}
it { get("/projects/testproject/repository/revisions/2457/diff.diff").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :rev => '2457', :format => 'diff')}
end
it { get("/projects/testproject/repository/diff").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject')}
it { get("/projects/testproject/repository/diff/path/to/file.c").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :path => "path/to/file", :format => 'c')}
it { get("/projects/testproject/repository/revisions/2/diff/path/to/file.c").should route_to( :controller => 'repositories', :action => 'diff', :id => 'testproject', :path => "path/to/file.c", :rev => '2')}
end
pending describe "browse" do
it { get("/projects/testproject/repository/browse/path/to/file.c").should route_to( :controller => 'repositories', :action => 'browse', :id => 'testproject', :path => %w[path to file.c])}
describe "browse" do
it { get("/projects/testproject/repository/browse/path/to/file.c").should route_to( :controller => 'repositories', :action => 'browse', :id => 'testproject', :path => "path/to/file", :format => 'c')}
end
pending describe "entry" do
it { get("/projects/testproject/repository/entry/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => %w[path to file.c])}
it { get("/projects/testproject/repository/revisions/2/entry/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => %w[path to file.c], :rev => '2')}
it { get("/projects/testproject/repository/raw/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => %w[path to file.c], :format => 'raw')}
it { get("/projects/testproject/repository/revisions/2/raw/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => %w[path to file.c], :rev => '2', :format => 'raw')}
describe "entry" do
it { get("/projects/testproject/repository/entry/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => "path/to/file", :format => 'c')}
it { get("/projects/testproject/repository/revisions/2/entry/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => "path/to/file", :rev => '2', :format => 'c')}
it { get("/projects/testproject/repository/raw/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => "path/to/file", :format => 'c', :kind => 'raw')}
it { get("/projects/testproject/repository/revisions/2/raw/path/to/file.c").should route_to( :controller => 'repositories', :action => 'entry', :id => 'testproject', :path => "path/to/file", :rev => '2', :format => 'c', :kind => 'raw')}
end
pending describe "annotate" do
it { get("/projects/testproject/repository/annotate/path/to/file.c").should route_to( :controller => 'repositories', :action => 'annotate', :id => 'testproject', :path => %w[path to file.c])}
describe "annotate" do
it { get("/projects/testproject/repository/annotate/path/to/file.c").should route_to( :controller => 'repositories', :action => 'annotate', :id => 'testproject', :path => "path/to/file", :format => 'c')}
end
pending describe "changes" do
it { get("/projects/testproject/repository/changes/path/to/file.c").should route_to( :controller => 'repositories', :action => 'changes', :id => 'testproject', :path => %w[path to file.c])}
describe "changes" do
it { get("/projects/testproject/repository/changes/path/to/file.c").should route_to( :controller => 'repositories', :action => 'changes', :id => 'testproject', :path => "path/to/file", :format => 'c')}
end
describe "stats" do

@ -9,6 +9,9 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
#
# The spec is still using the planning-elements, although internally only work_packages are used.
#
require File.expand_path('../../../../../spec_helper', __FILE__)
describe 'api/v2/planning_elements/_planning_element.api' do
@ -30,7 +33,7 @@ describe 'api/v2/planning_elements/_planning_element.api' do
let(:project) { FactoryGirl.create(:project, :id => 4711,
:identifier => 'test_project',
:name => 'Test Project') }
let(:planning_element) { FactoryGirl.build(:planning_element,
let(:planning_element) { FactoryGirl.build(:work_package,
:id => 1,
:project_id => project.id,
:subject => 'Awesometastic Planning Element',
@ -39,8 +42,6 @@ describe 'api/v2/planning_elements/_planning_element.api' do
:start_date => Date.parse('2011-12-06'),
:due_date => Date.parse('2011-12-13'),
:planning_element_status_comment => 'All going well',
:created_at => Time.parse('Thu Jan 06 12:35:00 +0100 2011'),
:updated_at => Time.parse('Fri Jan 07 12:35:00 +0100 2011')) }
@ -92,17 +93,7 @@ describe 'api/v2/planning_elements/_planning_element.api' do
it 'does not contain a planning_element_type node' do
render
response.should_not have_selector('planning_element planning_element_type')
end
it 'does not contain a planning_element_status node' do
render
response.should_not have_selector('planning_element planning_element_status')
end
it 'contains a planning_element_status_comment node containing the planning element status comment' do
render
response.should have_selector('planning_element planning_element_status_comment', :text => 'All going well')
response.should_not have_selector('planning_element type')
end
it 'contains a created_at element containing the planning element created_at in UTC in ISO 8601' do
@ -120,11 +111,11 @@ describe 'api/v2/planning_elements/_planning_element.api' do
describe 'with a planning element having a parent' do
let(:project) { FactoryGirl.create(:project) }
let(:parent_element) { FactoryGirl.create(:planning_element,
let(:parent_element) { FactoryGirl.create(:work_package,
:id => 1337,
:subject => 'Parent Element',
:project_id => project.id) }
let(:planning_element) { FactoryGirl.build(:planning_element,
let(:planning_element) { FactoryGirl.build(:work_package,
:parent_id => parent_element.id,
:project_id => project.id) }
@ -136,16 +127,16 @@ describe 'api/v2/planning_elements/_planning_element.api' do
describe 'with a planning element having children' do
let(:project) { FactoryGirl.create(:project) }
let(:planning_element) { FactoryGirl.create(:planning_element,
let(:planning_element) { FactoryGirl.create(:work_package,
:id => 1338,
:project_id => project.id) }
before do
FactoryGirl.create(:planning_element,
FactoryGirl.create(:work_package,
:project_id => project.id,
:parent_id => planning_element.id,
:id => 1339,
:subject => 'Child #1')
FactoryGirl.create(:planning_element,
FactoryGirl.create(:work_package,
:project_id => project.id,
:parent_id => planning_element.id,
:id => 1340,
@ -169,7 +160,7 @@ describe 'api/v2/planning_elements/_planning_element.api' do
:id => 1341,
:firstname => 'Paul',
:lastname => 'McCartney') }
let(:planning_element) { FactoryGirl.build(:planning_element,
let(:planning_element) { FactoryGirl.build(:work_package,
:responsible_id => responsible.id) }
it 'renders a responsible node containing the responsible\'s id and name' do
@ -182,7 +173,7 @@ describe 'api/v2/planning_elements/_planning_element.api' do
let(:type) { FactoryGirl.create(:type,
:id => 1342,
:name => 'Typ A') }
let(:planning_element) { FactoryGirl.build(:planning_element,
let(:planning_element) { FactoryGirl.build(:work_package,
:type_id => type.id) }
it 'renders a planning_element_type node containing the type\'s id and name' do
@ -192,11 +183,11 @@ describe 'api/v2/planning_elements/_planning_element.api' do
end
describe 'with a planning element having a planning element status' do
let(:planning_element_status) { FactoryGirl.create(:planning_element_status,
let(:planning_element_status) { FactoryGirl.create(:issue_status,
:id => 1343,
:name => 'All well') }
let(:planning_element) { FactoryGirl.build(:planning_element,
:planning_element_status_id => planning_element_status.id) }
let(:planning_element) { FactoryGirl.build(:work_package,
:status_id => planning_element_status.id) }
it 'renders a planning_element_status node containing the status\'s id and name' do
render
@ -205,7 +196,7 @@ describe 'api/v2/planning_elements/_planning_element.api' do
end
describe "a destroyed planning element" do
let(:planning_element) { FactoryGirl.create(:planning_element) }
let(:planning_element) { FactoryGirl.create(:work_package) }
before do
planning_element.destroy
end

@ -23,7 +23,7 @@ describe 'api/v2/planning_elements/destroy.api.rsb' do
params[:format] = 'xml'
end
let(:planning_element) { FactoryGirl.build(:planning_element) }
let(:planning_element) { FactoryGirl.build(:work_package) }
describe 'with an assigned planning element' do
it 'renders a planning_element document' do

@ -38,9 +38,9 @@ describe 'api/v2/planning_elements/index.api.rsb' do
describe 'with 3 planning elements available' do
let(:planning_elements) {
[ FactoryGirl.build(:planning_element),
FactoryGirl.build(:planning_element),
FactoryGirl.build(:planning_element)
[ FactoryGirl.build(:work_package),
FactoryGirl.build(:work_package),
FactoryGirl.build(:work_package)
]
}

@ -23,7 +23,7 @@ describe 'api/v2/planning_elements/show.api.rsb' do
params[:format] = 'xml'
end
let(:planning_element) { FactoryGirl.build(:planning_element) }
let(:planning_element) { FactoryGirl.build(:work_package) }
describe 'with an assigned planning element' do
it 'renders a planning_element document' do

@ -51,7 +51,7 @@ class RepositoriesFilesystemControllerTest < ActionController::TestCase
end
def test_show_no_extension
get :entry, :id => PRJ_ID, :path => ['test']
get :entry, :id => PRJ_ID, :path => 'test'
assert_response :success
assert_template 'entry'
assert_tag :tag => 'th',
@ -61,14 +61,14 @@ class RepositoriesFilesystemControllerTest < ActionController::TestCase
end
def test_entry_download_no_extension
get :entry, :id => PRJ_ID, :path => ['test'], :format => 'raw'
get :entry, :id => PRJ_ID, :path => 'test', :format => 'raw'
assert_response :success
assert_equal 'application/octet-stream', @response.content_type
end
def test_show_non_ascii_contents
with_settings :repositories_encodings => 'UTF-8,EUC-JP' do
get :entry, :id => PRJ_ID, :path => ['japanese', 'euc-jp.txt']
get :entry, :id => PRJ_ID, :path => 'japanese/euc-jp.txt'
assert_response :success
assert_template 'entry'
assert_tag :tag => 'th',
@ -80,7 +80,7 @@ class RepositoriesFilesystemControllerTest < ActionController::TestCase
def test_show_utf16
with_settings :repositories_encodings => 'UTF-16' do
get :entry, :id => PRJ_ID, :path => ['japanese', 'utf-16.txt']
get :entry, :id => PRJ_ID, :path => 'japanese/utf-16.txt'
assert_response :success
assert_select "tr" do
@ -95,7 +95,7 @@ class RepositoriesFilesystemControllerTest < ActionController::TestCase
def test_show_text_file_should_send_if_too_big
with_settings :file_max_size_displayed => 1 do
get :entry, :id => PRJ_ID, :path => ['japanese', 'big-file.txt']
get :entry, :id => PRJ_ID, :path => 'japanese/big-file.txt'
assert_response :success
assert_equal 'text/plain', @response.content_type
end

@ -101,7 +101,7 @@ class RepositoriesGitControllerTest < ActionController::TestCase
def test_browse_directory
@repository.fetch_changesets
@repository.reload
get :show, :id => 3, :path => ['images']
get :show, :id => 3, :path => 'images'
assert_response :success
assert_template 'show'
assert_not_nil assigns(:entries)
@ -117,7 +117,7 @@ class RepositoriesGitControllerTest < ActionController::TestCase
def test_browse_at_given_revision
@repository.fetch_changesets
@repository.reload
get :show, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
get :show, :id => 3, :path => 'images', :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
assert_response :success
assert_template 'show'
assert_not_nil assigns(:entries)
@ -127,14 +127,14 @@ class RepositoriesGitControllerTest < ActionController::TestCase
end
def test_changes
get :changes, :id => 3, :path => ['images', 'edit.png']
get :changes, :id => 3, :path => 'images/edit.png'
assert_response :success
assert_template 'changes'
assert_tag :tag => 'h2', :content => 'edit.png'
end
def test_entry_show
get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
get :entry, :id => 3, :path => 'sources/watchers_controller.rb'
assert_response :success
assert_template 'entry'
# Line 19
@ -145,14 +145,14 @@ class RepositoriesGitControllerTest < ActionController::TestCase
end
def test_entry_download
get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
get :entry, :id => 3, :path => 'sources/watchers_controller.rb', :format => 'raw'
assert_response :success
# File content
assert @response.body.include?('WITHOUT ANY WARRANTY')
end
def test_directory_entry
get :entry, :id => 3, :path => ['sources']
get :entry, :id => 3, :path => 'sources'
assert_response :success
assert_template 'show'
assert_not_nil assigns(:entry)
@ -191,7 +191,7 @@ class RepositoriesGitControllerTest < ActionController::TestCase
end
def test_annotate
get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
get :annotate, :id => 3, :path => 'sources/watchers_controller.rb'
assert_response :success
assert_template 'annotate'
# Line 23, changeset 2f9c0091
@ -204,14 +204,14 @@ class RepositoriesGitControllerTest < ActionController::TestCase
def test_annotate_at_given_revision
@repository.fetch_changesets
@repository.reload
get :annotate, :id => 3, :rev => 'deff7', :path => ['sources', 'watchers_controller.rb']
get :annotate, :id => 3, :rev => 'deff7', :path => 'sources/watchers_controller.rb'
assert_response :success
assert_template 'annotate'
assert_tag :tag => 'h2', :content => /@ deff712f/
end
def test_annotate_binary_file
get :annotate, :id => 3, :path => ['images', 'edit.png']
get :annotate, :id => 3, :path => 'images/edit.png'
assert_response 500
assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
:content => /cannot be annotated/

@ -68,7 +68,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_browse_directory
@repository.fetch_changesets
@repository.reload
get :show, :id => PRJ_ID, :path => ['subversion_test']
get :show, :id => PRJ_ID, :path => 'subversion_test'
assert_response :success
assert_template 'show'
assert_not_nil assigns(:entries)
@ -82,7 +82,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_browse_at_given_revision
@repository.fetch_changesets
@repository.reload
get :show, :id => PRJ_ID, :path => ['subversion_test'], :rev => 4
get :show, :id => PRJ_ID, :path => 'subversion_test', :rev => 4
assert_response :success
assert_template 'show'
assert_not_nil assigns(:entries)
@ -92,7 +92,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_file_changes
@repository.fetch_changesets
@repository.reload
get :changes, :id => PRJ_ID, :path => ['subversion_test', 'folder', 'helloworld.rb' ]
get :changes, :id => PRJ_ID, :path => 'subversion_test/folder/helloworld.rb'
assert_response :success
assert_template 'changes'
@ -114,7 +114,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_directory_changes
@repository.fetch_changesets
@repository.reload
get :changes, :id => PRJ_ID, :path => ['subversion_test', 'folder' ]
get :changes, :id => PRJ_ID, :path => 'subversion_test/folder'
assert_response :success
assert_template 'changes'
@ -126,7 +126,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_entry
@repository.fetch_changesets
@repository.reload
get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c']
get :entry, :id => PRJ_ID, :path => 'subversion_test/helloworld.c'
assert_response :success
assert_template 'entry'
end
@ -136,7 +136,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
@repository.reload
# no files in the test repo is larger than 1KB...
with_settings :file_max_size_displayed => 0 do
get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c']
get :entry, :id => PRJ_ID, :path => 'subversion_test/helloworld.c'
assert_response :success
assert_template nil
assert_equal 'attachment; filename="helloworld.c"', @response.headers['Content-Disposition']
@ -146,7 +146,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_entry_at_given_revision
@repository.fetch_changesets
@repository.reload
get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.rb'], :rev => 2
get :entry, :id => PRJ_ID, :path => 'subversion_test/helloworld.rb', :rev => 2
assert_response :success
assert_template 'entry'
# this line was removed in r3 and file was moved in r6
@ -157,7 +157,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_entry_not_found
@repository.fetch_changesets
@repository.reload
get :entry, :id => PRJ_ID, :path => ['subversion_test', 'zzz.c']
get :entry, :id => PRJ_ID, :path => 'subversion_test/zzz.c'
assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ },
:content => /The entry or revision was not found in the repository/
end
@ -165,7 +165,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_entry_download
@repository.fetch_changesets
@repository.reload
get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c'], :format => 'raw'
get :entry, :id => PRJ_ID, :path => 'subversion_test/helloworld.c', :format => 'raw'
assert_response :success
assert_template nil
assert_equal 'attachment; filename="helloworld.c"', @response.headers['Content-Disposition']
@ -174,7 +174,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_directory_entry
@repository.fetch_changesets
@repository.reload
get :entry, :id => PRJ_ID, :path => ['subversion_test', 'folder']
get :entry, :id => PRJ_ID, :path => 'subversion_test/folder'
assert_response :success
assert_template 'show'
assert_not_nil assigns(:entry)
@ -192,7 +192,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
:child => { :tag => 'li',
# link to the entry at rev 2
:child => { :tag => 'a',
:attributes => {:href => '/projects/ecookbook/repository/revisions/2/raw/test/some/path/in/the/repo'},
:attributes => {:href => '/projects/ecookbook/repository/revisions/2/entry/test/some/path/in/the/repo'},
:content => 'repo',
# link to partial diff
:sibling => { :tag => 'a',
@ -239,7 +239,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
:child => { :tag => 'li',
# link to the entry at rev 2
:child => { :tag => 'a',
:attributes => {:href => '/projects/ecookbook/repository/revisions/2/raw/path/in/the/repo'},
:attributes => {:href => '/projects/ecookbook/repository/revisions/2/entry/path/in/the/repo'},
:content => 'repo',
# link to partial diff
:sibling => { :tag => 'a',
@ -262,7 +262,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_directory_diff
@repository.fetch_changesets
@repository.reload
get :diff, :id => PRJ_ID, :rev => 6, :rev_to => 2, :path => ['subversion_test', 'folder']
get :diff, :id => PRJ_ID, :rev => 6, :rev_to => 2, :path => 'subversion_test/folder'
assert_response :success
assert_template 'diff'
@ -277,7 +277,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_annotate
@repository.fetch_changesets
@repository.reload
get :annotate, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c']
get :annotate, :id => PRJ_ID, :path => 'subversion_test/helloworld.c'
assert_response :success
assert_template 'annotate'
end
@ -285,7 +285,7 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase
def test_annotate_at_given_revision
@repository.fetch_changesets
@repository.reload
get :annotate, :id => PRJ_ID, :rev => 8, :path => ['subversion_test', 'helloworld.c']
get :annotate, :id => PRJ_ID, :rev => 8, :path => 'subversion_test/helloworld.c'
assert_response :success
assert_template 'annotate'
assert_tag :tag => 'h2', :content => /@ 8/

@ -294,13 +294,6 @@ class WikiControllerTest < ActionController::TestCase
get :rename, :project_id => 1, :id => 'Another_page'
assert_response :success
assert_template 'rename'
assert_tag 'option',
:attributes => {:value => ''},
:content => '',
:parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
assert_no_tag 'option',
:attributes => {:selected => 'selected'},
:parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
end
def test_get_rename_child_page
@ -308,17 +301,6 @@ class WikiControllerTest < ActionController::TestCase
get :rename, :project_id => 1, :id => 'Child_1'
assert_response :success
assert_template 'rename'
assert_tag 'option',
:attributes => {:value => ''},
:content => '',
:parent => {:tag => 'select', :attributes => {:name => 'wiki_page[parent_id]'}}
assert_tag 'option',
:attributes => {:value => '2', :selected => 'selected'},
:content => /Another page/,
:parent => {
:tag => 'select',
:attributes => {:name => 'wiki_page[parent_id]'}
}
end
def test_rename_with_redirect
@ -344,26 +326,6 @@ class WikiControllerTest < ActionController::TestCase
assert_nil wiki.find_page('Another page')
end
def test_rename_with_parent_assignment
@request.session[:user_id] = 2
put :rename, :project_id => 1, :id => 'Another_page',
:wiki_page => { :title => 'Another page',
:redirect_existing_links => "0",
:parent_id => '4' }
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Another_page'
assert_equal WikiPage.find(4), WikiPage.find_by_title('Another_page').parent
end
def test_rename_with_parent_unassignment
@request.session[:user_id] = 2
put :rename, :project_id => 1, :id => 'Child_1',
:wiki_page => { :title => 'Child 1',
:redirect_existing_links => "0",
:parent_id => '' }
assert_redirected_to :action => 'show', :project_id => 'ecookbook', :id => 'Child_1'
assert_nil WikiPage.find_by_title('Child_1').parent
end
def test_destroy_child
@request.session[:user_id] = 2
delete :destroy, :project_id => 1, :id => 'Child_1'

Loading…
Cancel
Save