FIX: Make category-drop work with lazy_load_categories (#24187)
The category drop was rerendered after every category async change because it updated the categories list. This is not necessary and categories can be referenced indirectly by ID instead.
This commit is contained in:
parent
21d614215b
commit
e85a81f33c
|
@ -10,20 +10,7 @@ export default Component.extend({
|
||||||
editingCategory: false,
|
editingCategory: false,
|
||||||
editingCategoryTab: null,
|
editingCategoryTab: null,
|
||||||
|
|
||||||
@discourseComputed("categories")
|
@discourseComputed("category.ancestors", "categories", "noSubcategories")
|
||||||
filteredCategories(categories) {
|
|
||||||
return categories.filter(
|
|
||||||
(category) =>
|
|
||||||
this.siteSettings.allow_uncategorized_topics ||
|
|
||||||
category.id !== this.site.uncategorized_category_id
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed(
|
|
||||||
"category.ancestors",
|
|
||||||
"filteredCategories",
|
|
||||||
"noSubcategories"
|
|
||||||
)
|
|
||||||
categoryBreadcrumbs(categoryAncestors, filteredCategories, noSubcategories) {
|
categoryBreadcrumbs(categoryAncestors, filteredCategories, noSubcategories) {
|
||||||
categoryAncestors = categoryAncestors || [];
|
categoryAncestors = categoryAncestors || [];
|
||||||
const parentCategories = [undefined, ...categoryAncestors];
|
const parentCategories = [undefined, ...categoryAncestors];
|
||||||
|
@ -44,7 +31,7 @@ export default Component.extend({
|
||||||
options,
|
options,
|
||||||
isSubcategory: !!parentCategory,
|
isSubcategory: !!parentCategory,
|
||||||
noSubcategories: !category && noSubcategories,
|
noSubcategories: !category && noSubcategories,
|
||||||
hasOptions: options.length !== 0,
|
hasOptions: !parentCategory || parentCategory.has_children,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,16 +22,24 @@ export default Component.extend({
|
||||||
|
|
||||||
// Should be a `readOnly` instead but some themes/plugins still pass
|
// Should be a `readOnly` instead but some themes/plugins still pass
|
||||||
// the `categories` property into this component
|
// the `categories` property into this component
|
||||||
@discourseComputed("site.categoriesList")
|
@discourseComputed()
|
||||||
categories(categoriesList) {
|
categories() {
|
||||||
|
let categories = this.site.categoriesList;
|
||||||
|
|
||||||
|
if (!this.siteSettings.allow_uncategorized_topics) {
|
||||||
|
categories = categories.filter(
|
||||||
|
(category) => category.id !== this.site.uncategorized_category_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentUser?.indirectly_muted_category_ids) {
|
if (this.currentUser?.indirectly_muted_category_ids) {
|
||||||
return categoriesList.filter(
|
categories = categories.filter(
|
||||||
(category) =>
|
(category) =>
|
||||||
!this.currentUser.indirectly_muted_category_ids.includes(category.id)
|
!this.currentUser.indirectly_muted_category_ids.includes(category.id)
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return categoriesList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return categories;
|
||||||
},
|
},
|
||||||
|
|
||||||
@discourseComputed("category")
|
@discourseComputed("category")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { get } from "@ember/object";
|
import { get } from "@ember/object";
|
||||||
import { htmlSafe } from "@ember/template";
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import categoryVariables from "discourse/helpers/category-variables";
|
||||||
import { isRTL } from "discourse/lib/text-direction";
|
import { isRTL } from "discourse/lib/text-direction";
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
import Category from "discourse/models/category";
|
import Category from "discourse/models/category";
|
||||||
|
@ -180,5 +181,9 @@ export function defaultCategoryLinkRenderer(category, opts) {
|
||||||
})}
|
})}
|
||||||
</span>`;
|
</span>`;
|
||||||
}
|
}
|
||||||
return `<${tagName} class="badge-category__wrapper ${extraClasses}" ${href}>${html}</${tagName}>${afterBadgeWrapper}`;
|
|
||||||
|
const style = categoryVariables(category);
|
||||||
|
const extraAttrs = style.string ? `style="${style}"` : "";
|
||||||
|
|
||||||
|
return `<${tagName} class="badge-category__wrapper ${extraClasses}" ${extraAttrs} ${href}>${html}</${tagName}>${afterBadgeWrapper}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,6 +488,22 @@ Category.reopenClass({
|
||||||
return category;
|
return category;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async asyncFindBySlugPathWithID(slugPathWithID) {
|
||||||
|
const result = await ajax("/categories/find", {
|
||||||
|
data: {
|
||||||
|
category_slug_path_with_id: slugPathWithID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result["ancestors"]) {
|
||||||
|
result["ancestors"].map((category) =>
|
||||||
|
Site.current().updateCategory(category)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Site.current().updateCategory(result.category);
|
||||||
|
},
|
||||||
|
|
||||||
findBySlugPathWithID(slugPathWithID) {
|
findBySlugPathWithID(slugPathWithID) {
|
||||||
let parts = slugPathWithID.split("/").filter(Boolean);
|
let parts = slugPathWithID.split("/").filter(Boolean);
|
||||||
// slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded
|
// slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded
|
||||||
|
|
|
@ -124,6 +124,10 @@ const Site = RestModel.extend({
|
||||||
newCategory = this.store.createRecord("category", newCategory);
|
newCategory = this.store.createRecord("category", newCategory);
|
||||||
categories.pushObject(newCategory);
|
categories.pushObject(newCategory);
|
||||||
this.categoriesById[categoryId] = newCategory;
|
this.categoriesById[categoryId] = newCategory;
|
||||||
|
newCategory.set(
|
||||||
|
"parentCategory",
|
||||||
|
this.categoriesById[newCategory.parent_category_id]
|
||||||
|
);
|
||||||
return newCategory;
|
return newCategory;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,7 @@ import I18n from "discourse-i18n";
|
||||||
class AbstractCategoryRoute extends DiscourseRoute {
|
class AbstractCategoryRoute extends DiscourseRoute {
|
||||||
@service composer;
|
@service composer;
|
||||||
@service router;
|
@service router;
|
||||||
|
@service siteSettings;
|
||||||
@service store;
|
@service store;
|
||||||
@service topicTrackingState;
|
@service topicTrackingState;
|
||||||
@service("search") searchService;
|
@service("search") searchService;
|
||||||
|
@ -29,9 +30,11 @@ class AbstractCategoryRoute extends DiscourseRoute {
|
||||||
controllerName = "discovery/list";
|
controllerName = "discovery/list";
|
||||||
|
|
||||||
async model(params, transition) {
|
async model(params, transition) {
|
||||||
const category = Category.findBySlugPathWithID(
|
const category = this.siteSettings.lazy_load_categories
|
||||||
params.category_slug_path_with_id
|
? await Category.asyncFindBySlugPathWithID(
|
||||||
);
|
params.category_slug_path_with_id
|
||||||
|
)
|
||||||
|
: Category.findBySlugPathWithID(params.category_slug_path_with_id);
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
this.router.replaceWith("/404");
|
this.router.replaceWith("/404");
|
||||||
|
|
|
@ -138,6 +138,7 @@ export default {
|
||||||
show_subcategory_list: true,
|
show_subcategory_list: true,
|
||||||
default_view: "latest",
|
default_view: "latest",
|
||||||
subcategory_list_style: "boxes_with_featured_topics",
|
subcategory_list_style: "boxes_with_featured_topics",
|
||||||
|
has_children: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
|
@ -158,6 +159,7 @@ export default {
|
||||||
background_url: null,
|
background_url: null,
|
||||||
show_subcategory_list: false,
|
show_subcategory_list: false,
|
||||||
default_view: "latest",
|
default_view: "latest",
|
||||||
|
has_children: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 24,
|
id: 24,
|
||||||
|
@ -309,6 +311,7 @@ export default {
|
||||||
notification_level: null,
|
notification_level: null,
|
||||||
show_subcategory_list: false,
|
show_subcategory_list: false,
|
||||||
default_view: "latest",
|
default_view: "latest",
|
||||||
|
has_children: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 11,
|
id: 11,
|
||||||
|
@ -463,6 +466,7 @@ export default {
|
||||||
default_view: "latest",
|
default_view: "latest",
|
||||||
subcategory_list_style: "boxes",
|
subcategory_list_style: "boxes",
|
||||||
default_list_filter: "all",
|
default_list_filter: "all",
|
||||||
|
has_children: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 240,
|
id: 240,
|
||||||
|
@ -516,6 +520,7 @@ export default {
|
||||||
parent_category_id: null,
|
parent_category_id: null,
|
||||||
notification_level: null,
|
notification_level: null,
|
||||||
background_url: null,
|
background_url: null,
|
||||||
|
has_children: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1002,
|
id: 1002,
|
||||||
|
@ -533,6 +538,7 @@ export default {
|
||||||
parent_category_id: 1001,
|
parent_category_id: 1001,
|
||||||
notification_level: null,
|
notification_level: null,
|
||||||
background_url: null,
|
background_url: null,
|
||||||
|
has_children: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1003,
|
id: 1003,
|
||||||
|
|
|
@ -54,8 +54,7 @@ export default ComboBoxComponent.extend({
|
||||||
return this.options.subCategory || false;
|
return this.options.subCategory || false;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
categoriesWithShortcuts: computed(
|
shortcuts: computed(
|
||||||
"categories.[]",
|
|
||||||
"value",
|
"value",
|
||||||
"selectKit.options.{subCategory,noSubcategories}",
|
"selectKit.options.{subCategory,noSubcategories}",
|
||||||
function () {
|
function () {
|
||||||
|
@ -82,11 +81,15 @@ export default ComboBoxComponent.extend({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = this._filterUncategorized(this.categories || []);
|
return shortcuts;
|
||||||
return shortcuts.concat(results);
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
|
categoriesWithShortcuts: computed("categories.[]", "shortcuts", function () {
|
||||||
|
const results = this._filterUncategorized(this.categories || []);
|
||||||
|
return this.shortcuts.concat(results);
|
||||||
|
}),
|
||||||
|
|
||||||
modifyNoSelection() {
|
modifyNoSelection() {
|
||||||
if (this.selectKit.options.noSubcategories) {
|
if (this.selectKit.options.noSubcategories) {
|
||||||
return this.defaultItem(NO_CATEGORIES_ID, this.noCategoriesLabel);
|
return this.defaultItem(NO_CATEGORIES_ID, this.noCategoriesLabel);
|
||||||
|
@ -138,15 +141,17 @@ export default ComboBoxComponent.extend({
|
||||||
|
|
||||||
if (this.siteSettings.lazy_load_categories) {
|
if (this.siteSettings.lazy_load_categories) {
|
||||||
const results = await Category.asyncSearch(filter, { ...opts, limit: 5 });
|
const results = await Category.asyncSearch(filter, { ...opts, limit: 5 });
|
||||||
return results.sort((a, b) => {
|
return this.shortcuts.concat(
|
||||||
if (a.parent_category_id && !b.parent_category_id) {
|
results.sort((a, b) => {
|
||||||
return 1;
|
if (a.parent_category_id && !b.parent_category_id) {
|
||||||
} else if (!a.parent_category_id && b.parent_category_id) {
|
return 1;
|
||||||
return -1;
|
} else if (!a.parent_category_id && b.parent_category_id) {
|
||||||
} else {
|
return -1;
|
||||||
return 0;
|
} else {
|
||||||
}
|
return 0;
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter) {
|
if (filter) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ class CategoriesController < ApplicationController
|
||||||
redirect
|
redirect
|
||||||
find_by_slug
|
find_by_slug
|
||||||
visible_groups
|
visible_groups
|
||||||
|
find
|
||||||
search
|
search
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -299,6 +300,24 @@ class CategoriesController < ApplicationController
|
||||||
render json: success_json.merge(groups: groups || [])
|
render json: success_json.merge(groups: groups || [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find
|
||||||
|
category = Category.find_by_slug_path_with_id(params[:category_slug_path_with_id])
|
||||||
|
raise Discourse::NotFound if category.blank?
|
||||||
|
guardian.ensure_can_see!(category)
|
||||||
|
|
||||||
|
ancestors = Category.secured(guardian).with_ancestors(category.id).where.not(id: category.id)
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
category: SiteCategorySerializer.new(category, scope: guardian, root: nil),
|
||||||
|
ancestors:
|
||||||
|
ActiveModel::ArraySerializer.new(
|
||||||
|
ancestors,
|
||||||
|
scope: guardian,
|
||||||
|
each_serializer: SiteCategorySerializer,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def search
|
def search
|
||||||
term = params[:term].to_s.strip
|
term = params[:term].to_s.strip
|
||||||
parent_category_id = params[:parent_category_id].to_i if params[:parent_category_id].present?
|
parent_category_id = params[:parent_category_id].to_i if params[:parent_category_id].present?
|
||||||
|
@ -334,10 +353,7 @@ class CategoriesController < ApplicationController
|
||||||
) if term.present?
|
) if term.present?
|
||||||
|
|
||||||
categories =
|
categories =
|
||||||
categories.where(
|
categories.where(parent_category_id: parent_category_id) if parent_category_id.present?
|
||||||
"id = :id OR parent_category_id = :id",
|
|
||||||
id: parent_category_id,
|
|
||||||
) if parent_category_id.present?
|
|
||||||
|
|
||||||
categories =
|
categories =
|
||||||
categories.where.not(id: SiteSetting.uncategorized_category_id) if !include_uncategorized
|
categories.where.not(id: SiteSetting.uncategorized_category_id) if !include_uncategorized
|
||||||
|
@ -358,7 +374,7 @@ class CategoriesController < ApplicationController
|
||||||
END
|
END
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
categories.order(:id)
|
categories = categories.order(:id)
|
||||||
|
|
||||||
render json: categories, each_serializer: SiteCategorySerializer
|
render json: categories, each_serializer: SiteCategorySerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -107,12 +107,13 @@ class Category < ActiveRecord::Base
|
||||||
before_save :downcase_name
|
before_save :downcase_name
|
||||||
before_save :ensure_category_setting
|
before_save :ensure_category_setting
|
||||||
|
|
||||||
after_save :publish_discourse_stylesheet
|
|
||||||
after_save :publish_category
|
|
||||||
after_save :reset_topic_ids_cache
|
after_save :reset_topic_ids_cache
|
||||||
after_save :clear_subcategory_ids
|
after_save :clear_subcategory_ids
|
||||||
|
after_save :clear_parent_ids
|
||||||
after_save :clear_url_cache
|
after_save :clear_url_cache
|
||||||
after_save :update_reviewables
|
after_save :update_reviewables
|
||||||
|
after_save :publish_discourse_stylesheet
|
||||||
|
after_save :publish_category
|
||||||
|
|
||||||
after_save do
|
after_save do
|
||||||
if saved_change_to_uploaded_logo_id? || saved_change_to_uploaded_logo_dark_id? ||
|
if saved_change_to_uploaded_logo_id? || saved_change_to_uploaded_logo_dark_id? ||
|
||||||
|
@ -128,6 +129,8 @@ class Category < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
after_destroy :reset_topic_ids_cache
|
after_destroy :reset_topic_ids_cache
|
||||||
|
after_destroy :clear_subcategory_ids
|
||||||
|
after_destroy :clear_parent_ids
|
||||||
after_destroy :publish_category_deletion
|
after_destroy :publish_category_deletion
|
||||||
after_destroy :remove_site_settings
|
after_destroy :remove_site_settings
|
||||||
|
|
||||||
|
@ -197,6 +200,19 @@ class Category < ActiveRecord::Base
|
||||||
scope :post_create_allowed,
|
scope :post_create_allowed,
|
||||||
->(guardian) { scoped_to_permissions(guardian, POST_CREATION_PERMISSIONS) }
|
->(guardian) { scoped_to_permissions(guardian, POST_CREATION_PERMISSIONS) }
|
||||||
|
|
||||||
|
scope :with_ancestors, ->(id) { where(<<~SQL, id) }
|
||||||
|
id IN (
|
||||||
|
WITH RECURSIVE ancestors(category_id) AS (
|
||||||
|
SELECT ?
|
||||||
|
UNION
|
||||||
|
SELECT parent_category_id
|
||||||
|
FROM categories, ancestors
|
||||||
|
WHERE id = ancestors.category_id
|
||||||
|
)
|
||||||
|
SELECT category_id FROM ancestors
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
|
||||||
delegate :post_template, to: "self.class"
|
delegate :post_template, to: "self.class"
|
||||||
|
|
||||||
# permission is just used by serialization
|
# permission is just used by serialization
|
||||||
|
@ -843,9 +859,24 @@ class Category < ActiveRecord::Base
|
||||||
self.where("string_to_array(email_in, '|') @> ARRAY[?]", Email.downcase(email)).first
|
self.where("string_to_array(email_in, '|') @> ARRAY[?]", Email.downcase(email)).first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@has_children = DistributedCache.new("has_children")
|
||||||
|
|
||||||
|
def self.has_children?(category_id)
|
||||||
|
@@has_children.defer_get_set(category_id.to_s) do
|
||||||
|
Category.where(parent_category_id: category_id).exists?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def has_children?
|
def has_children?
|
||||||
@has_children ||= (id && Category.where(parent_category_id: id).exists?) ? :true : :false
|
!!id && Category.has_children?(id)
|
||||||
@has_children == :true
|
end
|
||||||
|
|
||||||
|
def self.clear_parent_ids
|
||||||
|
@@has_children.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_parent_ids
|
||||||
|
Category.clear_parent_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
def uncategorized?
|
def uncategorized?
|
||||||
|
|
|
@ -5,7 +5,7 @@ class Site
|
||||||
include ActiveModel::Serialization
|
include ActiveModel::Serialization
|
||||||
|
|
||||||
# Number of categories preloaded when lazy_load_categories is enabled
|
# Number of categories preloaded when lazy_load_categories is enabled
|
||||||
LAZY_LOAD_CATEGORIES_LIMIT = 50
|
LAZY_LOAD_CATEGORIES_LIMIT = 10
|
||||||
|
|
||||||
cattr_accessor :preloaded_category_custom_fields
|
cattr_accessor :preloaded_category_custom_fields
|
||||||
|
|
||||||
|
@ -120,10 +120,6 @@ class Site
|
||||||
)
|
)
|
||||||
categories << category
|
categories << category
|
||||||
end
|
end
|
||||||
|
|
||||||
if SiteSetting.lazy_load_categories && categories.size >= Site::LAZY_LOAD_CATEGORIES_LIMIT
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
with_children = Set.new
|
with_children = Set.new
|
||||||
|
@ -161,7 +157,11 @@ class Site
|
||||||
|
|
||||||
self.class.categories_callbacks.each { |callback| callback.call(categories, @guardian) }
|
self.class.categories_callbacks.each { |callback| callback.call(categories, @guardian) }
|
||||||
|
|
||||||
categories
|
if SiteSetting.lazy_load_categories
|
||||||
|
categories[0...Site::LAZY_LOAD_CATEGORIES_LIMIT]
|
||||||
|
else
|
||||||
|
categories
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class BasicCategorySerializer < ApplicationSerializer
|
||||||
:notification_level,
|
:notification_level,
|
||||||
:can_edit,
|
:can_edit,
|
||||||
:topic_template,
|
:topic_template,
|
||||||
:has_children,
|
:has_children?,
|
||||||
:sort_order,
|
:sort_order,
|
||||||
:sort_ascending,
|
:sort_ascending,
|
||||||
:show_subcategory_list,
|
:show_subcategory_list,
|
||||||
|
|
|
@ -1160,6 +1160,7 @@ Discourse::Application.routes.draw do
|
||||||
|
|
||||||
resources :categories, except: %i[show new edit]
|
resources :categories, except: %i[show new edit]
|
||||||
post "categories/reorder" => "categories#reorder"
|
post "categories/reorder" => "categories#reorder"
|
||||||
|
get "categories/find" => "categories#find"
|
||||||
get "categories/search" => "categories#search"
|
get "categories/search" => "categories#search"
|
||||||
|
|
||||||
scope path: "category/:category_id" do
|
scope path: "category/:category_id" do
|
||||||
|
|
|
@ -1298,6 +1298,8 @@ RSpec.describe Category do
|
||||||
let(:guardian) { Guardian.new(admin) }
|
let(:guardian) { Guardian.new(admin) }
|
||||||
fab!(:category)
|
fab!(:category)
|
||||||
|
|
||||||
|
before { Category.clear_parent_ids }
|
||||||
|
|
||||||
describe "when category is uncategorized" do
|
describe "when category is uncategorized" do
|
||||||
it "should return the reason" do
|
it "should return the reason" do
|
||||||
category = Category.find(SiteSetting.uncategorized_category_id)
|
category = Category.find(SiteSetting.uncategorized_category_id)
|
||||||
|
|
|
@ -79,10 +79,7 @@
|
||||||
"items": {}
|
"items": {}
|
||||||
},
|
},
|
||||||
"has_children": {
|
"has_children": {
|
||||||
"type": [
|
"type": "boolean"
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"sort_order": {
|
"sort_order": {
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
@ -82,10 +82,7 @@
|
||||||
"items": {}
|
"items": {}
|
||||||
},
|
},
|
||||||
"has_children": {
|
"has_children": {
|
||||||
"type": [
|
"type": "boolean"
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"sort_order": {
|
"sort_order": {
|
||||||
"type": [
|
"type": [
|
||||||
|
|
|
@ -1040,6 +1040,31 @@ RSpec.describe CategoriesController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#find" do
|
||||||
|
fab!(:category) { Fabricate(:category, name: "Foo") }
|
||||||
|
fab!(:subcategory) { Fabricate(:category, name: "Foobar", parent_category: category) }
|
||||||
|
|
||||||
|
it "returns the category" do
|
||||||
|
get "/categories/find.json",
|
||||||
|
params: {
|
||||||
|
category_slug_path_with_id: "#{category.slug}/#{category.id}",
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.parsed_body["category"]["id"]).to eq(category.id)
|
||||||
|
expect(response.parsed_body["ancestors"]).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the subcategory and ancestors" do
|
||||||
|
get "/categories/find.json",
|
||||||
|
params: {
|
||||||
|
category_slug_path_with_id: "#{subcategory.slug}/#{subcategory.id}",
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.parsed_body["category"]["id"]).to eq(subcategory.id)
|
||||||
|
expect(response.parsed_body["ancestors"].map { |c| c["id"] }).to eq([category.id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#search" do
|
describe "#search" do
|
||||||
fab!(:category) { Fabricate(:category, name: "Foo") }
|
fab!(:category) { Fabricate(:category, name: "Foo") }
|
||||||
fab!(:subcategory) { Fabricate(:category, name: "Foobar", parent_category: category) }
|
fab!(:subcategory) { Fabricate(:category, name: "Foobar", parent_category: category) }
|
||||||
|
@ -1066,9 +1091,8 @@ RSpec.describe CategoriesController do
|
||||||
it "returns categories" do
|
it "returns categories" do
|
||||||
get "/categories/search.json", params: { parent_category_id: category.id }
|
get "/categories/search.json", params: { parent_category_id: category.id }
|
||||||
|
|
||||||
expect(response.parsed_body["categories"].size).to eq(2)
|
expect(response.parsed_body["categories"].size).to eq(1)
|
||||||
expect(response.parsed_body["categories"].map { |c| c["name"] }).to contain_exactly(
|
expect(response.parsed_body["categories"].map { |c| c["name"] }).to contain_exactly(
|
||||||
"Foo",
|
|
||||||
"Foobar",
|
"Foobar",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue