Fix wording: Change "Favored" to "Favorited"

Favorite is the correct term in the context of expressing a preference
for a particular project / other OpenProject domain object.

Updates `ActsAsFavorable` to `ActsAsFavoritable`, as well as filenames,
identifiers and strings to:

    favored => favorited
    favorable => favoritable
    favoring => favoriting
This commit is contained in:
Alexander Brandon Coles
2025-09-08 16:23:19 +01:00
parent d74d764c4c
commit 2b144e8542
58 changed files with 370 additions and 369 deletions
@@ -84,7 +84,7 @@
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsell-colored", ml: 2) %>
<% end %>
<% if child_item.favored %>
<% if child_item.favorited %>
<%= render Primer::Beta::Octicon.new(icon: "star-fill", "aria-label": I18n.t(:label_favorite), classes: %w[op-submenu--item-mark op-primer--star-icon], ml: 2) %>
<% end %>
</span>
@@ -45,8 +45,8 @@
)
end
if can_toggle_favor?
if currently_favored?
if can_toggle_favorite?
if currently_favorited?
header.with_action_icon_button(
icon: "star-fill",
mobile_icon: "star-fill",
@@ -106,9 +106,9 @@ class Projects::IndexPageHeaderComponent < ApplicationComponent
query.persisted?
end
def can_toggle_favor? = query.persisted?
def can_toggle_favorite? = query.persisted?
def currently_favored? = query.favored_by?(current_user)
def currently_favorited? = query.favorited_by?(current_user)
def breadcrumb_items
[
@@ -48,7 +48,7 @@ class Projects::ProjectsFiltersComponent < Filter::FilterComponent
Queries::Filters::Shared::CustomFields::Base,
Queries::Projects::Filters::ActiveFilter,
Queries::Projects::Filters::CreatedAtFilter,
Queries::Projects::Filters::FavoredFilter,
Queries::Projects::Filters::FavoritedFilter,
Queries::Projects::Filters::IdFilter,
Queries::Projects::Filters::LatestActivityAtFilter,
Queries::Projects::Filters::ProjectPhaseAnyFilter,
+14 -14
View File
@@ -30,7 +30,7 @@
module Projects
class RowComponent < ::RowComponent
delegate :identifier, to: :project
delegate :favored_project_ids,
delegate :favorited_project_ids,
:project_phase_by_definition,
to: :table
@@ -47,25 +47,25 @@ module Projects
""
end
def favored
def favorited
render(Primer::Beta::IconButton.new(
icon: currently_favored? ? "star-fill" : "star",
icon: currently_favorited? ? "star-fill" : "star",
scheme: :invisible,
mobile_icon: currently_favored? ? "star-fill" : "star",
mobile_icon: currently_favorited? ? "star-fill" : "star",
size: :medium,
tag: :a,
tooltip_direction: :e,
href: helpers.build_favorite_path(project, format: :html),
data: { "turbo-method": currently_favored? ? :delete : :post },
classes: currently_favored? ? "op-primer--star-icon " : "op-project-row-component--favorite",
label: currently_favored? ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite),
aria: { label: currently_favored? ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite) },
data: { "turbo-method": currently_favorited? ? :delete : :post },
classes: currently_favorited? ? "op-primer--star-icon " : "op-project-row-component--favorite",
label: currently_favorited? ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite),
aria: { label: currently_favorited? ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite) },
test_selector: "project-list-favorite-button"
))
end
def currently_favored?
@currently_favored ||= favored_project_ids.include?(project.id)
def currently_favorited?
@currently_favorited ||= favorited_project_ids.include?(project.id)
end
def column_value(column)
@@ -215,10 +215,10 @@ module Projects
def additional_css_class(column)
if column.attribute == :name
"project--hierarchy #{project.archived? ? 'archived' : ''}"
"project--hierarchy #{'archived' if project.archived?}"
elsif %i[status_explanation description].include?(column.attribute)
"project-long-text-container"
elsif column.attribute == :favored
elsif column.attribute == :favorited
"-w-abs-45"
elsif custom_field_column?(column)
cf = column.custom_field
@@ -267,7 +267,7 @@ module Projects
end
def more_menu_favorite_item
return if currently_favored?
return if currently_favorited?
{
scheme: :default,
@@ -280,7 +280,7 @@ module Projects
end
def more_menu_unfavorite_item
return unless currently_favored?
return unless currently_favorited?
{
scheme: :default,
+2 -2
View File
@@ -191,8 +191,8 @@ module Projects
end
end
def favored_project_ids
@favored_project_ids ||= Favorite.where(user: current_user, favored_type: "Project").pluck(:favored_id)
def favorited_project_ids
@favorited_project_ids ||= Favorite.where(user: current_user, favorited_type: "Project").pluck(:favorited_id)
end
def project_phase_by_definition(definition, project)
+10 -10
View File
@@ -29,33 +29,33 @@
#++
class FavoritesController < ApplicationController
before_action :find_favored_by_object
before_action :find_favorited_by_object
before_action :require_login
no_authorization_required! :favorite, :unfavorite
def favorite
if @favored.visible?(User.current)
set_favored(User.current, true)
if @favorited.visible?(User.current)
set_favorited(User.current, true)
else
render_403
end
end
def unfavorite
set_favored(User.current, false)
set_favorited(User.current, false)
end
private
def find_favored_by_object
def find_favorited_by_object
model_name = params[:object_type]
klass = ::OpenProject::Acts::Favorable::Registry.instance(model_name)
@favored = klass&.find(params[:object_id])
render_404 unless @favored
klass = ::OpenProject::Acts::Favoritable::Registry.instance(model_name)
@favorited = klass&.find(params[:object_id])
render_404 unless @favorited
end
def set_favored(user, favored)
@favored.set_favored(user, favored:)
def set_favorited(user, favorited)
@favorited.set_favorited(user, favorited:)
respond_to do |format|
format.html { redirect_back(fallback_location: home_url, status: 303) }
+1 -1
View File
@@ -37,7 +37,7 @@ class HomescreenController < ApplicationController
def index
@newest_projects = Project.visible.newest.take(3)
@favorite_projects = Project.visible.active.favored_by(User.current)
@favorite_projects = Project.visible.active.favorited_by(User.current)
@newest_users = User.active.newest.take(3)
@news = News.latest(count: 3)
@announcement = Announcement.active_and_current
+2 -2
View File
@@ -347,7 +347,7 @@ module SortHelper
data = options.delete(:data) || {}
options[:title] = sort_header_title(column, caption, options) if with_title
options[:icon_only_header] = column == :favored
options[:icon_only_header] = column == :favorited
within_sort_header_tag_hierarchy(options, sort_class(column)) do
yield(column, caption, default_order, allowed_params:, param:, lang:, title: options[:title],
@@ -401,7 +401,7 @@ module SortHelper
content_args = html_options.merge(rel: :nofollow, param: nil)
render Primer::Alpha::ActionMenu.new(menu_id: "menu-#{attribute}") do |menu|
action_button(menu, column, caption, favorite: column == :favored)
action_button(menu, column, caption, favorite: column == :favorited)
# Some columns are not sortable or do not offer a suitable filter. Omit those actions for them.
sort_actions(menu, attribute, default_order, content_args:, allowed_params:, **html_options) if sortable
+7 -7
View File
@@ -68,8 +68,8 @@ module Projects
end
end
def favored?(query_params)
query_params[:query_id].in?(favored_ids)
def favorited?(query_params)
query_params[:query_id].in?(favorited_ids)
end
def query_path(query_params)
@@ -82,7 +82,7 @@ module Projects
static_filters [
ProjectQueries::Static::ACTIVE,
current_user.logged? ? ProjectQueries::Static::MY : nil,
current_user.logged? ? ProjectQueries::Static::FAVORED : nil,
current_user.logged? ? ProjectQueries::Static::FAVORITED : nil,
current_user.admin? ? ProjectQueries::Static::ARCHIVED : nil
].compact
end
@@ -116,12 +116,12 @@ module Projects
def persisted_filters
@persisted_filters ||= ::ProjectQuery
.visible(current_user)
.with_favored_by_user(current_user)
.order(favored: :desc, name: :asc)
.with_favorited_by_user(current_user)
.order(favorited: :desc, name: :asc)
end
def favored_ids
@favored_ids ||= persisted_filters.select(&:favored).to_set(&:id)
def favorited_ids
@favorited_ids ||= persisted_filters.select(&:favorited).to_set(&:id)
end
def modification_params?
+4 -3
View File
@@ -29,6 +29,7 @@
# ++
class Submenu
include Rails.application.routes.url_helpers
attr_reader :view_type, :project, :params
def initialize(view_type:, params:, project: nil)
@@ -117,7 +118,7 @@ class Submenu
icon: icon_map.fetch(icon_key, icon_key),
count:,
selected:,
favored: favored?(query_params),
favorited: favorited?(query_params),
show_enterprise_icon:)
end
@@ -129,14 +130,14 @@ class Submenu
end
end
if query_params.empty? && (%i[filters query_props query_id name].any? { |k| params.key? k })
if query_params.empty? && %i[filters query_props query_id name].any? { |k| params.key? k }
return false
end
true
end
def favored?(_query_params)
def favorited?(_query_params)
false
end
+1 -1
View File
@@ -30,7 +30,7 @@
class ApplicationRecord < ActiveRecord::Base
include ::OpenProject::Acts::Watchable
include ::OpenProject::Acts::Favorable
include ::OpenProject::Acts::Favoritable
self.abstract_class = true
+2 -2
View File
@@ -30,8 +30,8 @@
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :favored, polymorphic: true
belongs_to :favorited, polymorphic: true
validates :user, presence: true
validates :favored, presence: true
validates :favorited, presence: true
end
+1 -1
View File
@@ -108,7 +108,7 @@ class Project < ApplicationRecord
store_attribute :settings, :deactivate_work_package_attachments, :boolean
store_attribute :settings, :enabled_internal_comments, :boolean
acts_as_favorable
acts_as_favoritable
acts_as_customizable validate_on: :saving_custom_fields
# extended in Projects::CustomFields in order to support sections
+6 -6
View File
@@ -31,7 +31,7 @@
class ProjectQueries::Static
ACTIVE = "active"
MY = "my"
FAVORED = "favored"
FAVORITED = "favorited"
ARCHIVED = "archived"
ON_TRACK = "on_track"
OFF_TRACK = "off_track"
@@ -46,8 +46,8 @@ class ProjectQueries::Static
static_query_active
when MY
static_query_my
when FAVORED
static_query_favored
when FAVORITED
static_query_favorited
when ARCHIVED
static_query_archived
when ON_TRACK
@@ -73,9 +73,9 @@ class ProjectQueries::Static
end
end
def static_query_favored
list_with(:"projects.lists.favored") do |query|
query.where("favored", "=", OpenProject::Database::DB_VALUE_TRUE)
def static_query_favorited
list_with(:"projects.lists.favorited") do |query|
query.where("favorited", "=", OpenProject::Database::DB_VALUE_TRUE)
end
end
+1 -1
View File
@@ -36,7 +36,7 @@ class ProjectQuery < ApplicationRecord
belongs_to :user
acts_as_favorable
acts_as_favoritable
serialize :filters, coder: Queries::Serialization::Filters.new(self)
serialize :orders, coder: Queries::Serialization::Orders.new(self)
@@ -29,15 +29,15 @@
#++
module Projects::Exports
module Formatters
class Favored < ::Exports::Formatters::Default
class Favorited < ::Exports::Formatters::Default
def self.apply?(attribute, export_format)
export_format == :pdf && attribute.to_sym == :favored
export_format == :pdf && attribute.to_sym == :favorited
end
##
# Takes a project and returns yes/no depending on the favored attribute
# Takes a project and returns yes/no depending on the favorited attribute
def format(project, **)
project.favored_by?(User.current) ? I18n.t(:general_text_Yes) : I18n.t(:general_text_No)
project.favorited_by?(User.current) ? I18n.t(:general_text_Yes) : I18n.t(:general_text_No)
end
end
end
@@ -170,7 +170,7 @@ module Projects::Exports::PDFExport
end
def can_view_attribute?(_project, attribute)
attribute && %i[name favored].exclude?(attribute)
attribute && %i[name favorited].exclude?(attribute)
end
def user_can_view_project_phases?(project)
+2 -2
View File
@@ -36,7 +36,7 @@ module Queries::Projects
filter Filters::AvailableProjectAttributesFilter
filter Filters::CreatedAtFilter
filter Filters::CustomFieldFilter
filter Filters::FavoredFilter
filter Filters::FavoritedFilter
filter Filters::IdFilter
filter Filters::LatestActivityAtFilter
filter Filters::ProjectPhaseAnyFilter
@@ -67,7 +67,7 @@ module Queries::Projects
select Selects::CreatedAt
select Selects::CustomField
select Selects::Default
select Selects::Favored
select Selects::Favorited
select Selects::LatestActivityAt
select Selects::ProjectPhase
select Selects::RequiredDiskSpace
@@ -28,11 +28,11 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::Projects::Filters::FavoredFilter < Queries::Projects::Filters::Base
class Queries::Projects::Filters::FavoritedFilter < Queries::Projects::Filters::Base
include Queries::Filters::Shared::BooleanFilter
def self.key
:favored
:favorited
end
def human_name
@@ -46,9 +46,9 @@ class Queries::Projects::Filters::FavoredFilter < Queries::Projects::Filters::Ba
def apply_to(_query_scope)
if (values.first == OpenProject::Database::DB_VALUE_TRUE && operator_strategy == Queries::Operators::BooleanEquals) ||
(values.first == OpenProject::Database::DB_VALUE_FALSE && operator_strategy == Queries::Operators::BooleanNotEquals)
super.where(id: favored_project_ids)
super.where(id: favorited_project_ids)
else
super.where.not(id: favored_project_ids)
super.where.not(id: favorited_project_ids)
end
end
@@ -57,9 +57,9 @@ class Queries::Projects::Filters::FavoredFilter < Queries::Projects::Filters::Ba
nil
end
def favored_project_ids
def favorited_project_ids
Favorite
.where(favored_type: "Project", user_id: User.current.id)
.select(:favored_id)
.where(favorited_type: "Project", user_id: User.current.id)
.select(:favorited_id)
end
end
@@ -28,9 +28,9 @@
# See COPYRIGHT and LICENSE files for more details.
# ++
class Queries::Projects::Selects::Favored < Queries::Selects::Base
class Queries::Projects::Selects::Favorited < Queries::Selects::Base
def self.key
:favored
:favorited
end
def self.available?
@@ -94,6 +94,6 @@ class ProjectQueries::SetAttributesService < BaseServices::SetAttributes
end
def default_columns
(["favored", "name"] + Setting.enabled_projects_columns).uniq
(["favorited", "name"] + Setting.enabled_projects_columns).uniq
end
end
@@ -2,7 +2,7 @@
<div class="op-widget-box--body">
<% if @favorite_projects.any? %>
<p class="widget-box--additional-info"><%= t("projects.lists.favored") %></p>
<p class="widget-box--additional-info"><%= t("projects.lists.favorited") %></p>
<ul class="widget-box--arrow-links">
<% @favorite_projects.each do |project| %>
<li>
+1 -1
View File
@@ -463,7 +463,7 @@ module Settings
default: false
},
enabled_projects_columns: {
default: %w[favored name project_status public created_at latest_activity_at required_disk_space],
default: %w[favorited name project_status public created_at latest_activity_at required_disk_space],
allowed: -> { ProjectQuery.new.available_selects.map { |s| s.attribute.to_s } }
},
enabled_scm: {
@@ -30,10 +30,10 @@
# Be sure to restart your server when you modify this file.
# For development and non-eager load mode, we need to load models using acts_as_favorable manually
# For development and non-eager load mode, we need to load models using acts_as_favoritable manually
# as no eager loading takes place
Rails.application.config.to_prepare do
OpenProject::Acts::Favorable::Registry.add(
OpenProject::Acts::Favoritable::Registry.add(
Project,
ProjectQuery,
reset: true
+1 -1
View File
@@ -57,7 +57,7 @@ Rails.application.configure do |application|
formatter Project, Projects::Exports::Formatters::Description
formatter Project, Projects::Exports::Formatters::Public
formatter Project, Projects::Exports::Formatters::Active
formatter Project, Projects::Exports::Formatters::Favored
formatter Project, Projects::Exports::Formatters::Favorited
formatter Project, Projects::Exports::Formatters::RequiredDiskSpace
end
end
+1 -1
View File
@@ -463,7 +463,7 @@ en:
lists:
active: "Active projects"
my: "My projects"
favored: "Favorite projects"
favorited: "Favorite projects"
archived: "Archived projects"
shared: "Shared project lists"
my_lists: "My project lists"
+1 -1
View File
@@ -238,7 +238,7 @@ Rails.application.routes.draw do
end
# generic route for adding/removing favorites
scope ":object_type/:object_id", constraints: OpenProject::Acts::Favorable::RouteConstraint do
scope ":object_type/:object_id", constraints: OpenProject::Acts::Favoritable::RouteConstraint do
post "/favorite" => "favorites#favorite"
delete "/favorite" => "favorites#unfavorite"
end
@@ -206,7 +206,7 @@ OPENPROJECT_EMAILS__FOOTER (default={"en" => ""}) Emails footer
OPENPROJECT_EMAILS__HEADER (default={"en" => ""}) Emails header
OPENPROJECT_EMAILS__SALUTATION (default=:firstname) Address user in emails with
OPENPROJECT_ENABLE__INTERNAL__ASSETS__SERVER (default=false) Serve assets through the Rails internal asset server
OPENPROJECT_ENABLED__PROJECTS__COLUMNS (default=["favored", "name", "project_status", "public", "created_at", "latest_activity_at", "required_disk_space"]) Columns in a projects list displayed by default
OPENPROJECT_ENABLED__PROJECTS__COLUMNS (default=["favorited", "name", "project_status", "public", "created_at", "latest_activity_at", "required_disk_space"]) Columns in a projects list displayed by default
OPENPROJECT_ENABLED__SCM (default=["subversion", "git"]) Enabled SCM
OPENPROJECT_ENFORCE__TRACKING__START__AND__END__TIMES (default=false) Require start and finish times
OPENPROJECT_ENTERPRISE__CHARGEBEE__SITE (default="openproject-enterprise") Site name for EE trial service
@@ -50,7 +50,7 @@ export class WidgetProjectFavoritesComponent extends AbstractWidgetComponent imp
ngOnInit() {
const filters = new ApiV3FilterBuilder();
filters.add('favored', '=', true);
filters.add('favorited', '=', true);
filters.add('active', '=', true);
this.projects$ = this
@@ -43,8 +43,8 @@
*ngIf="projects$ | async as projects"
>
<spot-text-field
*ngIf="displayMode !== 'favored' || (favorites$ | async)?.length > 0"
[placeholder]="displayMode === 'favored' ? text.search_favorites_placeholder : text.search_placeholder"
*ngIf="displayMode !== 'favorited' || (favorites$ | async)?.length > 0"
[placeholder]="displayMode === 'favorited' ? text.search_favorites_placeholder : text.search_placeholder"
[(ngModel)]="searchableProjectListService.searchText"
[ngModelOptions]="{standalone: true}"
data-test-selector="op-header-project-select--search"
@@ -66,7 +66,7 @@
class="spot-list_active"
[projects]="projects"
[displayMode]="displayMode"
[favored]="favorites$ | async"
[favorited]="favorites$ | async"
[searchText]="searchableProjectListService.searchText"
[root]="true"
data-list-root="true"
@@ -74,7 +74,7 @@
></ul>
<ng-template #noResultsTemplate>
<div *ngIf="displayMode === 'favored' && (favorites$ | async).length === 0"
<div *ngIf="displayMode === 'favorited' && (favorites$ | async).length === 0"
class="op-header-project-select--no-favorites"
>
<svg
@@ -89,9 +89,9 @@
[textContent]="text.no_favorites_subtext"></span>
</p>
</div>
<span *ngIf="!(displayMode === 'favored' && (favorites$ | async).length === 0)"
<span *ngIf="!(displayMode === 'favorited' && (favorites$ | async).length === 0)"
class="op-project-list-modal--no-results">
{{ displayMode === 'favored' ? text.no_favorite_results : text.no_results }}
{{ displayMode === 'favorited' ? text.no_favorite_results : text.no_results }}
</span>
</ng-template>
</ng-container>
@@ -110,7 +110,7 @@ export class OpHeaderProjectSelectComponent extends UntilDestroyedMixin implemen
.apiV3Service
.projects
.signalled(
ApiV3Filter('favored', '=', true),
ApiV3Filter('favorited', '=', true),
[
'elements/id',
],
@@ -125,7 +125,7 @@ export class OpHeaderProjectSelectComponent extends UntilDestroyedMixin implemen
public text = {
all: this.I18n.t('js.label_all_uppercase'),
favored: this.I18n.t('js.label_favorites'),
favorited: this.I18n.t('js.label_favorites'),
no_favorites: this.I18n.t('js.favorite_projects.no_results'),
no_favorites_subtext: this.I18n.t('js.favorite_projects.no_results_subtext'),
project: {
@@ -140,11 +140,11 @@ export class OpHeaderProjectSelectComponent extends UntilDestroyedMixin implemen
no_favorite_results: this.I18n.t('js.include_projects.no_favorite_results'),
};
public displayMode:'all'|'favored';
public displayMode:'all'|'favorited';
public displayModeOptions = [
{ value: 'all', title: this.text.all },
{ value: 'favored', title: this.text.favored },
{ value: 'favorited', title: this.text.favorited },
];
/* This seems like a way too convoluted loading check, but there's a good reason we need it.
@@ -197,7 +197,7 @@ export class OpHeaderProjectSelectComponent extends UntilDestroyedMixin implemen
}
ngOnInit():void {
const stored = window.OpenProject.guardedLocalStorage(this.displayModeLocalStorageKey) as 'all'|'favored'|undefined;
const stored = window.OpenProject.guardedLocalStorage(this.displayModeLocalStorageKey) as 'all'|'favorited'|undefined;
this.displayMode = stored || 'all';
}
@@ -211,7 +211,7 @@ export class OpHeaderProjectSelectComponent extends UntilDestroyedMixin implemen
});
}
displayModeChange(mode:'all'|'favored'):void {
displayModeChange(mode:'all'|'favorited'):void {
this.displayMode = mode;
window.OpenProject.guardedLocalStorage(this.displayModeLocalStorageKey, mode);
@@ -26,7 +26,7 @@
[textContent]="project.name"></span>
<svg
*ngIf="favored?.includes(project.id.toString())"
*ngIf="favorited?.includes(project.id.toString())"
star-fill-icon
class="op-primer--star-icon"
size="small"
@@ -61,7 +61,7 @@
op-header-project-select-list
[projects]="project.children"
[displayMode]="displayMode"
[favored]="favored"
[favorited]="favorited"
[selected]="selected"
[searchText]="searchText"
></ul>
@@ -38,7 +38,7 @@ export class OpHeaderProjectSelectListComponent implements OnInit, OnChanges {
@Input() projects:IProjectData[] = [];
@Input() favored:string[] = [];
@Input() favorited:string[] = [];
@Input() displayMode:string;
@@ -78,7 +78,7 @@ export class OpHeaderProjectSelectListComponent implements OnInit, OnChanges {
}
ngOnChanges(changes:SimpleChanges) {
if (changes.displayMode || changes.projects || changes.favored) {
if (changes.displayMode || changes.projects || changes.favorited) {
this.updateProjectFilter();
}
}
@@ -89,20 +89,20 @@ export class OpHeaderProjectSelectListComponent implements OnInit, OnChanges {
return true;
}
return this.showWhenFavored(project);
return this.showWhenFavorited(project);
});
}
showWhenFavored(project:IProjectData):boolean {
if (this.isFavored(project)) {
showWhenFavorited(project:IProjectData):boolean {
if (this.isFavorited(project)) {
return true;
}
return project.children.length > 0 && project.children.some((child) => this.showWhenFavored(child));
return project.children.length > 0 && project.children.some((child) => this.showWhenFavorited(child));
}
isFavored(project:IProjectData):boolean {
return this.favored.includes(project.id.toString());
isFavorited(project:IProjectData):boolean {
return this.favorited.includes(project.id.toString());
}
extendedUrl(projectId:string|null):string {
+2 -2
View File
@@ -31,8 +31,8 @@
module OpenProject
module Menu
MenuGroup = Data.define(:header, :children)
MenuItem = Data.define(:title, :href, :selected, :favored, :icon, :count, :show_enterprise_icon) do
def initialize(title:, href:, selected:, favored: false, icon: nil, count: nil, show_enterprise_icon: false)
MenuItem = Data.define(:title, :href, :selected, :favorited, :icon, :count, :show_enterprise_icon) do
def initialize(title:, href:, selected:, favorited: false, icon: nil, count: nil, show_enterprise_icon: false)
super
end
end
@@ -28,50 +28,50 @@
module OpenProject
module Acts
module Favorable
module Favoritable
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
# Marks an ActiveRecord::Model as favorable
# A favorable model has association with users (watchers) that marked it as favorite.
# Marks an ActiveRecord::Model as favoritable
# A favoritable model has association with users (watchers) that marked it as favorite.
#
# This also creates the routes necessary for favoring/unfavoring by
# This also creates the routes necessary for favoriting/unfavoriting by
# adding the model's name to routes. This e.g leads to the following
# routes when marking issues as watchable:
# POST: projects/identifier/favorite
# DELETE: projects/identifier/favorite
#
# acts_as_favorable expects that the including module defines a +visible?(user)+ method,
# acts_as_favoritable expects that the including module defines a +visible?(user)+ method,
# as it's used to identify whether a user can actually favorite the object.
def acts_as_favorable # rubocop:disable Metrics/AbcSize
def acts_as_favoritable # rubocop:disable Metrics/AbcSize
return if included_modules.include?(InstanceMethods)
class_eval do
prepend InstanceMethods
has_many :favorites, as: :favored, dependent: :delete_all, validate: false
has_many :favoring_users, through: :favorites, source: :user, validate: false
has_many :favorites, as: :favorited, dependent: :delete_all, validate: false
has_many :favoriting_users, through: :favorites, source: :user, validate: false
scope :favored_by, ->(user_id) {
scope :favorited_by, ->(user_id) {
includes(:favorites)
.where(favorites: { user_id: })
}
scope :with_favored_by_user, ->(user) {
scope :with_favorited_by_user, ->(user) {
favorite = ::Favorite.arel_table
join = arel_table
.join(favorite, Arel::Nodes::OuterJoin)
.on(
favorite[:favored_type].eq(base_class.name),
favorite[:favored_id].eq(arel_table[:id]),
favorite[:favorited_type].eq(base_class.name),
favorite[:favorited_id].eq(arel_table[:id]),
favorite[:user_id].eq(user.id)
)
.join_sources
select(arel_table[Arel.star], "(favorites.id IS NOT NULL) AS favored").joins(join)
select(arel_table[Arel.star], "(favorites.id IS NOT NULL) AS favorited").joins(join)
}
end
@@ -80,21 +80,21 @@ module OpenProject
end
module InstanceMethods
def add_favoring_user(user)
def add_favoriting_user(user)
return if favorites.exists?(user_id: user.id)
favorites << Favorite.new(user:, favored: self)
favorites << Favorite.new(user:, favorited: self)
end
def remove_favoring_user(user)
def remove_favoriting_user(user)
favorites.where(user:).delete_all
end
def set_favored(user, favored: true)
favored ? add_favoring_user(user) : remove_favoring_user(user)
def set_favorited(user, favorited: true)
favorited ? add_favoriting_user(user) : remove_favoriting_user(user)
end
def favored_by?(user)
def favorited_by?(user)
favorites.exists?(user:)
end
end
@@ -28,7 +28,7 @@
module OpenProject
module Acts
module Favorable
module Favoritable
module Registry
extend RegistryMethods
end
@@ -28,7 +28,7 @@
module OpenProject
module Acts
module Favorable
module Favoritable
module RouteConstraint
def self.matches?(request)
params = request.path_parameters
@@ -27,8 +27,8 @@ MenuGroup = Data.define(:header, :children)
* `OpenProject::Menu::MenuItem`: A concrete Item with all options it supports
```ruby
MenuItem = Data.define(:title, :href, :selected, :favored, :icon, :count, :show_enterprise_icon) do
def initialize(title:, href:, selected:, favored: false, icon: nil, count: nil, show_enterprise_icon: false)
MenuItem = Data.define(:title, :href, :selected, :favorited, :icon, :count, :show_enterprise_icon) do
def initialize(title:, href:, selected:, favorited: false, icon: nil, count: nil, show_enterprise_icon: false)
super
end
end
@@ -42,13 +42,13 @@ module OpenProject
# @display min_height 450px
# @param searchable [Boolean]
# @param with_create_button [Boolean]
# @param favored [Boolean]
# @param favorited [Boolean]
# @param count [Integer]
# @param show_enterprise_icon [Boolean]
# @param icon [Symbol] octicon
def playground(searchable: false,
with_create_button: false,
favored: false,
favorited: false,
count: nil,
show_enterprise_icon: false,
icon: nil)
@@ -56,7 +56,7 @@ module OpenProject
locals: {
searchable:,
create_btn_options: with_create_button ? { href: "/#", module_key: "user" } : nil,
favored:,
favorited:,
count:,
show_enterprise_icon:,
icon:
@@ -3,7 +3,7 @@
OpenProject::Menu::MenuGroup.new(
header: "Private",
children: [
OpenProject::Menu::MenuItem.new(title: "My query", href: "", selected: false, favored: favored),
OpenProject::Menu::MenuItem.new(title: "My query", href: "", selected: false, favorited: favorited),
OpenProject::Menu::MenuItem.new(title: "An awesome query", href: "", selected: false, count: count, icon: icon),
OpenProject::Menu::MenuItem.new(title: "A third query", href: "", selected: false, show_enterprise_icon: show_enterprise_icon)
]
@@ -7,17 +7,17 @@
t("overviews.label")
]
)
favored = @project.favored_by?(User.current)
favorited = @project.favorited_by?(User.current)
header.with_action_icon_button(
icon: favored ? "star-fill" : "star",
mobile_icon: favored ? "star-fill" : "star",
icon: favorited ? "star-fill" : "star",
mobile_icon: favorited ? "star-fill" : "star",
size: :medium,
tag: :a,
href: build_favorite_path(@project, format: :html),
data: { method: favored ? :delete : :post },
classes: favored ? "op-primer--star-icon" : "",
label: favored ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite),
aria: { label: favored ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite) },
data: { method: favorited ? :delete : :post },
classes: favorited ? "op-primer--star-icon" : "",
label: favorited ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite),
aria: { label: favorited ? I18n.t(:button_unfavorite) : I18n.t(:button_favorite) },
test_selector: "project-favorite-button"
)
@@ -40,7 +40,7 @@ RSpec.describe Projects::RowComponent, type: :component do
let(:project) { build_stubbed(:project, name: "My Project No. 1", identifier: "myproject_no_1") }
let(:table) do
instance_double(Projects::TableComponent, columns: [Queries::Projects::Selects::Default.new(:name)],
favored_project_ids: [])
favorited_project_ids: [])
end
let(:user) { build_stubbed(:user) }
+8 -8
View File
@@ -66,17 +66,17 @@ RSpec.describe "Favorite projects", :js, :selenium do
expect(page).to have_css "a", accessible_name: "Remove from favorite"
project.reload
expect(project).to be_favored_by(user)
expect(project).to be_favorited_by(user)
projects_page.visit!
projects_page.open_filters
projects_page.filter_by_favored "yes"
projects_page.filter_by_favorited "yes"
expect(page).to have_text "My favorite!"
projects_page.visit!
projects_page.open_filters
projects_page.filter_by_favored "no"
projects_page.filter_by_favorited "no"
expect(page).to have_no_text "My favorite!"
@@ -114,10 +114,10 @@ RSpec.describe "Favorite projects", :js, :selenium do
top_menu.expect_current_mode "All"
end
context "when project is favored" do
context "when project is favorited" do
before do
project.add_favoring_user(user)
other_project.add_favoring_user(user)
project.add_favoriting_user(user)
other_project.add_favoriting_user(user)
other_project.update! active: false
end
@@ -137,7 +137,7 @@ RSpec.describe "Favorite projects", :js, :selenium do
context "when favoriting only one subproject" do
before do
project.update! parent: other_project
project.add_favoring_user(user)
project.add_favoriting_user(user)
end
it "still shows up in top menu (Regression #54729)" do
@@ -171,7 +171,7 @@ RSpec.describe "Favorite projects", :js, :selenium do
ProjectRole.anonymous.update permissions: [:view_project]
end
it "does not shows favored projects" do
it "does not shows favorited projects" do
visit project_path(project)
retry_block do
+4 -4
View File
@@ -239,7 +239,7 @@ RSpec.describe "Projects lists table display and actions", :js, with_settings: {
end
visit project_path(project)
expect(project).to be_favored_by(admin)
expect(project).to be_favorited_by(admin)
visit projects_path
projects_page.activate_menu_of(project) do |menu|
@@ -248,7 +248,7 @@ RSpec.describe "Projects lists table display and actions", :js, with_settings: {
end
visit project_path(project)
expect(project).not_to be_favored_by(admin)
expect(project).not_to be_favorited_by(admin)
visit projects_path
projects_page.within_row(project) do
@@ -260,7 +260,7 @@ RSpec.describe "Projects lists table display and actions", :js, with_settings: {
projects_page.activate_menu_of(project) do |menu|
expect(menu).to have_text("Remove from favorites")
end
expect(project).to be_favored_by(admin)
expect(project).to be_favorited_by(admin)
projects_page.within_row(project) do
page.find_test_selector("project-list-favorite-button").click
@@ -269,7 +269,7 @@ RSpec.describe "Projects lists table display and actions", :js, with_settings: {
projects_page.activate_menu_of(project) do |menu|
expect(menu).to have_text("Add to favorites")
end
expect(project).not_to be_favored_by(admin)
expect(project).not_to be_favorited_by(admin)
end
specify "project can be deleted" do
@@ -411,17 +411,17 @@ RSpec.describe "Persisted lists on projects index page",
projects_page.expect_no_columns("Public")
end
it "allows favoring persisted query" do
projects_page.expect_sidebar_filter("Persisted query", favored: false)
it "allows favoriting persisted query" do
projects_page.expect_sidebar_filter("Persisted query", favorited: false)
projects_page.set_sidebar_filter("Persisted query")
projects_page.expect_sidebar_filter("Persisted query", selected: true, favored: false)
projects_page.expect_sidebar_filter("Persisted query", selected: true, favorited: false)
projects_page.mark_query_favorite
projects_page.expect_sidebar_filter("Persisted query", selected: true, favored: true)
projects_page.expect_sidebar_filter("Persisted query", selected: true, favorited: true)
projects_page.unmark_query_favorite
projects_page.expect_sidebar_filter("Persisted query", selected: true, favored: false)
projects_page.expect_sidebar_filter("Persisted query", selected: true, favorited: false)
end
it "loads the query with a custom field filter (Regression#57298)" do
@@ -30,7 +30,7 @@
require "spec_helper"
RSpec.describe OpenProject::Acts::Favorable::RouteConstraint do
RSpec.describe OpenProject::Acts::Favoritable::RouteConstraint do
let(:request) { instance_double(ActionDispatch::Request, path_parameters:) }
let(:path_parameters) { { object_id: id, object_type: type } }
+12 -12
View File
@@ -78,7 +78,7 @@ RSpec.describe Projects::Menu do
end
it "has the favorite projects item" do
expect(children_menu_items).to include(have_attributes(title: I18n.t("projects.lists.favored")))
expect(children_menu_items).to include(have_attributes(title: I18n.t("projects.lists.favorited")))
end
it "has an archived projects item" do
@@ -96,7 +96,7 @@ RSpec.describe Projects::Menu do
end
it "has the favorite projects item" do
expect(children_menu_items).to include(have_attributes(title: I18n.t("projects.lists.favored")))
expect(children_menu_items).to include(have_attributes(title: I18n.t("projects.lists.favorited")))
end
it "has no archived projects item" do
@@ -136,7 +136,7 @@ RSpec.describe Projects::Menu do
end
it "has no favorite projects item" do
expect(children_menu_items).not_to include(have_attributes(title: I18n.t("projects.lists.favored")))
expect(children_menu_items).not_to include(have_attributes(title: I18n.t("projects.lists.favorited")))
end
it "has no archived projects item" do
@@ -163,13 +163,13 @@ RSpec.describe Projects::Menu do
end
before do
favored_queries.each do |query|
query.add_favoring_user(current_user)
favorited_queries.each do |query|
query.add_favoriting_user(current_user)
end
end
context "when no queries are favored" do
let(:favored_queries) { [] }
context "when no queries are favorited" do
let(:favorited_queries) { [] }
it "orders persisted titles alphabetically" do
expect(titles).to eq(
@@ -183,8 +183,8 @@ RSpec.describe Projects::Menu do
end
end
context "when some queries are favored" do
let(:favored_queries) do
context "when some queries are favorited" do
let(:favorited_queries) do
[
current_user_query,
public_query,
@@ -192,7 +192,7 @@ RSpec.describe Projects::Menu do
]
end
it "orders persisted titles by favor then alphabetically" do
it "orders persisted titles by favorite then alphabetically" do
expect(titles).to eq(
[
["Active projects", "My projects", "Favorite projects"],
@@ -204,8 +204,8 @@ RSpec.describe Projects::Menu do
end
end
context "when all queries are favored" do
let(:favored_queries) do
context "when all queries are favorited" do
let(:favorited_queries) do
[
current_user_query,
another_current_user_query,
+1 -1
View File
@@ -472,7 +472,7 @@ RSpec.describe Project do
end
end
it_behaves_like "acts_as_favorable included" do
it_behaves_like "acts_as_favoritable included" do
let(:instance) { project }
end
@@ -33,7 +33,7 @@ require "services/base_services/behaves_like_create_service"
RSpec.describe Queries::Factory,
"ProjectQuery",
with_settings: { enabled_projects_columns: %w[favored name project_status] } do
with_settings: { enabled_projects_columns: %w[favorited name project_status] } do
before do
scope = instance_double(ActiveRecord::Relation)
@@ -52,7 +52,7 @@ RSpec.describe Queries::Factory,
build_stubbed(:project_query, name: "My query") do |query|
query.order(id: :asc)
query.where(:project_status_code, "=", [Project.status_codes[:on_track].to_s])
query.select(:project_status, :name, :favored)
query.select(:project_status, :name, :favorited)
end
end
let(:custom_field) do
@@ -343,7 +343,7 @@ RSpec.describe Queries::Factory,
it "has the enabled_project_columns columns as selects" do
expect(find.selects.map(&:attribute))
.to eq(%i[project_status name favored])
.to eq(%i[project_status name favorited])
end
it { is_expected.not_to be_changed }
@@ -654,7 +654,7 @@ RSpec.describe Queries::Factory,
it "has the selects of the persisted query" do
expect(find.selects.map(&:attribute))
.to eq(%i[project_status name favored])
.to eq(%i[project_status name favorited])
end
it { is_expected.to be_changed }
@@ -699,7 +699,7 @@ RSpec.describe Queries::Factory,
it "has the selects of the persisted query" do
expect(find.selects.map(&:attribute))
.to eq(%i[project_status name favored])
.to eq(%i[project_status name favorited])
end
it { is_expected.to be_changed }
@@ -805,7 +805,7 @@ RSpec.describe Queries::Factory,
it "keeps selects" do
expect(find.selects.map(&:attribute))
.to eq(%i[project_status name favored])
.to eq(%i[project_status name favorited])
end
it { is_expected.to be_changed }
@@ -36,7 +36,7 @@ RSpec.describe ProjectQuery do
shared_let(:user) { create(:user) }
shared_let(:admin) { create(:admin) }
it_behaves_like "acts_as_favorable included" do
it_behaves_like "acts_as_favoritable included" do
let(:instance) { create(:project_query) }
end
@@ -137,7 +137,7 @@ RSpec.describe ProjectQuery do
id
identifier
name
favored
favorited
public
description
hierarchy
@@ -162,7 +162,7 @@ RSpec.describe ProjectQuery do
id
identifier
name
favored
favorited
public
description
hierarchy
@@ -230,56 +230,56 @@ RSpec.describe "API v3 Project resource index", content_type: :json do
end
end
context "when filtering for favored" do
let(:favored_project) { create(:project) }
let(:unfavored_project) { create(:project) }
context "when filtering for favorited" do
let(:favorited_project) { create(:project) }
let(:unfavorited_project) { create(:project) }
let(:projects) { [favored_project, unfavored_project] }
let(:projects) { [favorited_project, unfavorited_project] }
current_user do
create(:user, member_with_roles: { favored_project => role,
unfavored_project => role }) do |user|
favored_project.set_favored(user)
create(:user, member_with_roles: { favorited_project => role,
unfavorited_project => role }) do |user|
favorited_project.set_favorited(user)
end
end
context "when filtering for favorite projects" do
let(:filters) do
[{ favored: { operator: "=", values: ["t"] } }]
[{ favorited: { operator: "=", values: ["t"] } }]
end
it_behaves_like "API V3 collection response", 1, 1, "Project" do
let(:elements) { [favored_project] }
let(:elements) { [favorited_project] }
end
end
context "when filtering for nonfavorite projects" do
let(:filters) do
[{ favored: { operator: "=", values: ["f"] } }]
[{ favorited: { operator: "=", values: ["f"] } }]
end
it_behaves_like "API V3 collection response", 1, 1, "Project" do
let(:elements) { [unfavored_project] }
let(:elements) { [unfavorited_project] }
end
end
context "when not filtering for favorite projects" do
let(:filters) do
[{ favored: { operator: "!", values: ["t"] } }]
[{ favorited: { operator: "!", values: ["t"] } }]
end
it_behaves_like "API V3 collection response", 1, 1, "Project" do
let(:elements) { [unfavored_project] }
let(:elements) { [unfavorited_project] }
end
end
context "when not filtering for nonfavorite projects" do
let(:filters) do
[{ favored: { operator: "!", values: ["f"] } }]
[{ favorited: { operator: "!", values: ["f"] } }]
end
it_behaves_like "API V3 collection response", 1, 1, "Project" do
let(:elements) { [favored_project] }
let(:elements) { [favorited_project] }
end
end
end
@@ -179,7 +179,7 @@ RSpec.describe ProjectQueries::SetAttributesService, type: :model do
subject
expect(model_instance.selects.map(&:attribute))
.to eql %i[favored name cf_1]
.to eql %i[favorited name cf_1]
end
it "assigns default selects for admin",
@@ -191,7 +191,7 @@ RSpec.describe ProjectQueries::SetAttributesService, type: :model do
subject
expect(model_instance.selects.map(&:attribute))
.to eql %i[favored name created_at cf_1]
.to eql %i[favorited name created_at cf_1]
end
# rubocop:enable Naming/VariableNumber
end
+3 -3
View File
@@ -38,16 +38,16 @@ module Components
expect(page).to have_css('[data-test-selector="op-submenu"]')
end
def expect_item(name, selected: false, favored: nil, visible: true)
def expect_item(name, selected: false, favorited: nil, visible: true)
within "#main-menu" do
selected_specifier = selected ? ".selected" : ":not(.selected)"
if favored.nil?
if favorited.nil?
expect(page).to have_css(".op-submenu--item-action#{selected_specifier}", text: name, visible:)
else
item = page.find(".op-submenu--item-action#{selected_specifier}", text: name, visible:)
if favored
if favorited
expect(item).to have_css(".op-primer--star-icon")
else
expect(item).to have_no_css(".op-primer--star-icon")
+5 -5
View File
@@ -86,8 +86,8 @@ module Pages
expect(page).to have_css('[data-test-selector="project-query-name"]', text: name)
end
def expect_sidebar_filter(filter_name, selected: false, favored: false, visible: true)
submenu.expect_item(filter_name, selected:, favored:, visible:)
def expect_sidebar_filter(filter_name, selected: false, favorited: false, visible: true)
submenu.expect_item(filter_name, selected:, favorited:, visible:)
end
def expect_no_sidebar_filter(filter_name)
@@ -221,8 +221,8 @@ module Pages
wait_for_reload
end
def filter_by_favored(value)
set_filter("favored", "Favorite", "is", [value])
def filter_by_favorited(value)
set_filter("favorited", "Favorite", "is", [value])
wait_for_reload
end
@@ -520,7 +520,7 @@ module Pages
private
def boolean_filter?(filter)
%w[active member_of favored public templated].include?(filter.to_s)
%w[active member_of favorited public templated].include?(filter.to_s)
end
def submenu
-161
View File
@@ -1,161 +0,0 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
RSpec.shared_examples_for "acts_as_favorable included" do
shared_let(:favoring_user) { create(:user) }
shared_let(:other_user) { create(:user) }
before do
Favorite.create(user: favoring_user, favored: instance)
end
it { is_expected.to have_many(:favorites).dependent(:delete_all) }
it { is_expected.to have_many(:favoring_users).through(:favorites) }
describe ".favored_by" do
it "returns instance for favoring user" do
expect(described_class.favored_by(favoring_user).to_a).to eq [instance]
end
it "returns no instance for non favoring user" do
expect(described_class.favored_by(other_user).to_a).not_to eq [instance]
end
end
describe ".with_favored_by_user" do
subject { described_class.with_favored_by_user(user).to_a }
context "for favoring user" do
let(:user) { favoring_user }
it "returns instance for favoring user" do
expect(subject).to eq [instance]
end
it "marks instance as favored" do
expect(subject).to all(have_attributes(favored: true))
end
end
context "for non favoring user" do
let(:user) { other_user }
it "returns instance for favoring user" do
expect(subject).to eq [instance]
end
it "marks instance as not favored" do
expect(subject).to all(have_attributes(favored: false))
end
end
end
describe "#add_favoring_user" do
context "for favoring user" do
let(:user) { favoring_user }
it "does nothing" do
expect do
instance.add_favoring_user(user)
end.not_to change { described_class.favored_by(user).to_a }.from([instance])
end
end
context "for non favoring user" do
let(:user) { other_user }
it "adds to favorites" do
expect do
instance.add_favoring_user(user)
end.to change { described_class.favored_by(user).to_a }.from([]).to([instance])
end
end
end
describe "#remove_favoring_user" do
context "for favoring user" do
let(:user) { favoring_user }
it "removes from favorites" do
expect do
instance.remove_favoring_user(user)
end.to change { described_class.favored_by(user).to_a }.from([instance]).to([])
end
end
context "for non favoring user" do
let(:user) { other_user }
it "does nothing" do
expect do
instance.remove_favoring_user(user)
end.not_to change { described_class.favored_by(user).to_a }
end
end
end
describe "#set_favored" do
before do
allow(instance).to receive(:add_favoring_user)
allow(instance).to receive(:remove_favoring_user)
end
it "calls add_favoring_user by default" do
instance.set_favored(favoring_user)
expect(instance).to have_received(:add_favoring_user).with(favoring_user)
expect(instance).not_to have_received(:remove_favoring_user)
end
it "calls add_favoring_user when called with favored: true" do
instance.set_favored(favoring_user, favored: true)
expect(instance).to have_received(:add_favoring_user).with(favoring_user)
expect(instance).not_to have_received(:remove_favoring_user)
end
it "calls remove_favoring_user when called with favored: false" do
instance.set_favored(favoring_user, favored: false)
expect(instance).not_to have_received(:add_favoring_user)
expect(instance).to have_received(:remove_favoring_user).with(favoring_user)
end
end
describe "#favored_by?" do
it "returns true for favoring user" do
expect(instance).to be_favored_by(favoring_user)
end
it "returns false for non favoring user" do
expect(instance).not_to be_favored_by(other_user)
end
end
end
+161
View File
@@ -0,0 +1,161 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
RSpec.shared_examples_for "acts_as_favoritable included" do
shared_let(:favoriting_user) { create(:user) }
shared_let(:other_user) { create(:user) }
before do
Favorite.create(user: favoriting_user, favorited: instance)
end
it { is_expected.to have_many(:favorites).dependent(:delete_all) }
it { is_expected.to have_many(:favoriting_users).through(:favorites) }
describe ".favorited_by" do
it "returns instance for favoriting user" do
expect(described_class.favorited_by(favoriting_user).to_a).to eq [instance]
end
it "returns no instance for non favoriting user" do
expect(described_class.favorited_by(other_user).to_a).not_to eq [instance]
end
end
describe ".with_favorited_by_user" do
subject { described_class.with_favorited_by_user(user).to_a }
context "for favoriting user" do
let(:user) { favoriting_user }
it "returns instance for favoriting user" do
expect(subject).to eq [instance]
end
it "marks instance as favorited" do
expect(subject).to all(have_attributes(favorited: true))
end
end
context "for non favoriting user" do
let(:user) { other_user }
it "returns instance for favoriting user" do
expect(subject).to eq [instance]
end
it "marks instance as not favorited" do
expect(subject).to all(have_attributes(favorited: false))
end
end
end
describe "#add_favoriting_user" do
context "for favoriting user" do
let(:user) { favoriting_user }
it "does nothing" do
expect do
instance.add_favoriting_user(user)
end.not_to change { described_class.favorited_by(user).to_a }.from([instance])
end
end
context "for non favoriting user" do
let(:user) { other_user }
it "adds to favorites" do
expect do
instance.add_favoriting_user(user)
end.to change { described_class.favorited_by(user).to_a }.from([]).to([instance])
end
end
end
describe "#remove_favoriting_user" do
context "for favoriting user" do
let(:user) { favoriting_user }
it "removes from favorites" do
expect do
instance.remove_favoriting_user(user)
end.to change { described_class.favorited_by(user).to_a }.from([instance]).to([])
end
end
context "for non favoriting user" do
let(:user) { other_user }
it "does nothing" do
expect do
instance.remove_favoriting_user(user)
end.not_to change { described_class.favorited_by(user).to_a }
end
end
end
describe "#set_favorited" do
before do
allow(instance).to receive(:add_favoriting_user)
allow(instance).to receive(:remove_favoriting_user)
end
it "calls add_favoriting_user by default" do
instance.set_favorited(favoriting_user)
expect(instance).to have_received(:add_favoriting_user).with(favoriting_user)
expect(instance).not_to have_received(:remove_favoriting_user)
end
it "calls add_favoriting_user when called with favorited: true" do
instance.set_favorited(favoriting_user, favorited: true)
expect(instance).to have_received(:add_favoriting_user).with(favoriting_user)
expect(instance).not_to have_received(:remove_favoriting_user)
end
it "calls remove_favoriting_user when called with favorited: false" do
instance.set_favorited(favoriting_user, favorited: false)
expect(instance).not_to have_received(:add_favoriting_user)
expect(instance).to have_received(:remove_favoriting_user).with(favoriting_user)
end
end
describe "#favorited_by?" do
it "returns true for favoriting user" do
expect(instance).to be_favorited_by(favoriting_user)
end
it "returns false for non favoriting user" do
expect(instance).not_to be_favorited_by(other_user)
end
end
end
@@ -477,12 +477,12 @@ RSpec.describe Principals::DeleteJob, type: :model do
describe "favorites" do
before do
project.add_favoring_user(principal)
project.add_favoriting_user(principal)
job
end
it "removes the assigned_to association to the principal" do
expect(project.favoring_users.reload).to be_empty
expect(project.favoriting_users.reload).to be_empty
end
end
end