+
+}
diff --git a/app/assets/javascripts/discourse/tests/fixtures/theme-setting-schema-data.js b/app/assets/javascripts/discourse/tests/fixtures/theme-setting-schema-data.js
index 1f8f41559c3..f1834bb068d 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/theme-setting-schema-data.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/theme-setting-schema-data.js
@@ -187,7 +187,7 @@ export default function schemaAndData(version = 1) {
type: "categories",
},
group_field: {
- type: "group",
+ type: "groups",
},
tags_field: {
type: "tags",
diff --git a/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs
index 12808e534e3..4e8c9fa4fe2 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs
+++ b/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs
@@ -859,25 +859,28 @@ module(
);
});
- test("input fields of type group", async function (assert) {
+ test("input fields of type groups", async function (assert) {
const setting = ThemeSettings.create({
setting: "objects_setting",
objects_schema: {
name: "something",
- identifier: "id",
properties: {
- required_group: {
- type: "group",
+ required_groups: {
+ type: "groups",
required: true,
},
- not_required_group: {
- type: "group",
+ groups_with_validations: {
+ type: "groups",
+ validations: {
+ min: 2,
+ max: 3,
+ },
},
},
},
value: [
{
- required_group: 6,
+ required_groups: [0, 1],
},
],
});
@@ -888,29 +891,59 @@ module(
const inputFields = new InputFieldsFromDOM();
- assert
- .dom(inputFields.fields.required_group.labelElement)
- .hasText("required_group*");
-
- let groupSelector = selectKit(
- `${inputFields.fields.required_group.selector} .select-kit`
+ let groupsSelector = selectKit(
+ `${inputFields.fields.required_groups.selector} .select-kit`
);
- assert.strictEqual(groupSelector.header().value(), "6");
- assert.dom(groupSelector.clearButton()).doesNotExist("is not clearable");
+ assert.strictEqual(groupsSelector.header().value(), "0,1");
- assert
- .dom(inputFields.fields.not_required_group.labelElement)
- .hasText("not_required_group");
+ await groupsSelector.expand();
+ await groupsSelector.deselectItemByValue("0");
+ await groupsSelector.deselectItemByValue("1");
+ await groupsSelector.collapse();
- groupSelector = selectKit(
- `${inputFields.fields.not_required_group.selector} .select-kit`
+ inputFields.refresh();
+
+ assert.dom(inputFields.fields.required_groups.errorElement).hasText(
+ I18n.t("admin.customize.theme.schema.fields.groups.at_least", {
+ count: 1,
+ })
);
- await groupSelector.expand();
- await groupSelector.selectRowByIndex(1);
+ assert
+ .dom(inputFields.fields.groups_with_validations.labelElement)
+ .hasText("groups_with_validations");
- assert.dom(groupSelector.clearButton()).exists("is clearable");
+ groupsSelector = selectKit(
+ `${inputFields.fields.groups_with_validations.selector} .select-kit`
+ );
+
+ assert.strictEqual(groupsSelector.header().value(), null);
+
+ await groupsSelector.expand();
+ await groupsSelector.selectRowByIndex(1);
+ await groupsSelector.collapse();
+
+ assert.strictEqual(groupsSelector.header().value(), "1");
+
+ inputFields.refresh();
+
+ assert
+ .dom(inputFields.fields.groups_with_validations.errorElement)
+ .hasText(
+ I18n.t("admin.customize.theme.schema.fields.groups.at_least", {
+ count: 2,
+ })
+ );
+
+ await groupsSelector.expand();
+ await groupsSelector.selectRowByIndex(2);
+ await groupsSelector.selectRowByIndex(3);
+ await groupsSelector.selectRowByIndex(4);
+
+ assert
+ .dom(groupsSelector.error())
+ .hasText("You can only select 3 items.");
});
test("generic identifier is used when identifier is not specified in the schema", async function (assert) {
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index bdc6b4b9786..3d89ad2c968 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -5654,6 +5654,10 @@ en:
back_button: "Back to %{name}"
fields:
required: "*required"
+ groups:
+ at_least:
+ one: "at least %{count} group is required"
+ other: "at least %{count} groups are required"
categories:
at_least:
one: "at least %{count} category is required"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 63939fbd8c9..513705baf03 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -170,8 +170,12 @@ en:
humanize_not_valid_post_value: "The property at JSON Pointer '%{property_json_pointer}' must be a valid post id."
not_valid_post_value: "must be a valid post id"
- humanize_not_valid_group_value: "The property at JSON Pointer '%{property_json_pointer}' must be a valid group id."
- not_valid_group_value: "must be a valid group id"
+ humanize_not_valid_groups_value: "The property at JSON Pointer '%{property_json_pointer}' must be an array of valid group ids."
+ not_valid_groups_value: "must be an array of valid group ids"
+ humanize_groups_value_not_valid_min: "The property at JSON Pointer '%{property_json_pointer}' must have at least %{min} group ids."
+ groups_value_not_valid_min: "must have at least %{min} group ids"
+ humanize_groups_value_not_valid_max: "The property at JSON Pointer '%{property_json_pointer}' must have at most %{max} group ids."
+ groups_value_not_valid_max: "must have at most %{max} group ids"
humanize_not_valid_tags_value: "The property at JSON Pointer '%{property_json_pointer}' must be an array of valid tag names."
not_valid_tags_value: "must be an array of valid tag names"
diff --git a/lib/theme_settings_object_validator.rb b/lib/theme_settings_object_validator.rb
index ad8c36d5c97..2435d06cf16 100644
--- a/lib/theme_settings_object_validator.rb
+++ b/lib/theme_settings_object_validator.rb
@@ -124,7 +124,7 @@ class ThemeSettingsObjectValidator
case type
when "string"
value.is_a?(String)
- when "integer", "topic", "post", "group", "upload"
+ when "integer", "topic", "post", "upload"
value.is_a?(Integer)
when "float"
value.is_a?(Float) || value.is_a?(Integer)
@@ -132,7 +132,7 @@ class ThemeSettingsObjectValidator
[true, false].include?(value)
when "enum"
property_attributes[:choices].include?(value)
- when "categories"
+ when "categories", "groups"
value.is_a?(Array) && value.all? { |id| id.is_a?(Integer) }
when "tags"
value.is_a?(Array) && value.all? { |tag| tag.is_a?(String) }
@@ -157,12 +157,12 @@ class ThemeSettingsObjectValidator
return true if value.nil?
case type
- when "topic", "upload", "post", "group"
+ when "topic", "upload", "post"
if !valid_ids(type).include?(value)
add_error(property_name, :"not_valid_#{type}_value")
return false
end
- when "tags", "categories"
+ when "tags", "categories", "groups"
if !Array(value).to_set.subset?(valid_ids(type))
add_error(property_name, :"not_valid_#{type}_value")
return false
@@ -240,7 +240,7 @@ class ThemeSettingsObjectValidator
"post" => {
klass: Post,
},
- "group" => {
+ "groups" => {
klass: Group,
},
"upload" => {
diff --git a/spec/lib/theme_settings_object_validator_spec.rb b/spec/lib/theme_settings_object_validator_spec.rb
index 0148aead697..b992b8a1c0d 100644
--- a/spec/lib/theme_settings_object_validator_spec.rb
+++ b/spec/lib/theme_settings_object_validator_spec.rb
@@ -845,19 +845,19 @@ RSpec.describe ThemeSettingsObjectValidator do
end
end
- context "for group properties" do
- it "should not return any error message when the value of the property is a valid id of a group record" do
+ context "for groups properties" do
+ it "should not return any error message when the value of the property is an array of valid group record ids" do
group = Fabricate(:group)
- schema = { name: "section", properties: { group_property: { type: "group" } } }
+ schema = { name: "section", properties: { groups_property: { type: "groups" } } }
expect(
- described_class.new(schema: schema, object: { group_property: group.id }).validate,
+ described_class.new(schema: schema, object: { groups_property: [group.id] }).validate,
).to eq({})
end
it "should not return any error messages when the value is not present and it's not required in the schema" do
- schema = { name: "section", properties: { group_property: { type: "group" } } }
+ schema = { name: "section", properties: { groups_property: { type: "groups" } } }
expect(described_class.new(schema: schema, object: {}).validate).to eq({})
end
@@ -865,44 +865,85 @@ RSpec.describe ThemeSettingsObjectValidator do
schema = {
name: "section",
properties: {
- group_property: {
- type: "group",
+ groups_property: {
+ type: "groups",
required: true,
},
},
}
errors = described_class.new(schema: schema, object: {}).validate
- expect(errors.keys).to eq(["/group_property"])
- expect(errors["/group_property"].full_messages).to contain_exactly("must be present")
+ expect(errors.keys).to eq(["/groups_property"])
+ expect(errors["/groups_property"].full_messages).to contain_exactly("must be present")
end
- it "should return the right hash of error messages when value of property is not an integer" do
- schema = { name: "section", properties: { group_property: { type: "group" } } }
+ it "should return the right hash of error messages when value of property is not an array of valid group ids" do
+ schema = { name: "section", properties: { groups_property: { type: "groups" } } }
- errors = described_class.new(schema: schema, object: { group_property: "string" }).validate
+ errors = described_class.new(schema: schema, object: { groups_property: "string" }).validate
- expect(errors.keys).to eq(["/group_property"])
+ expect(errors.keys).to eq(["/groups_property"])
- expect(errors["/group_property"].full_messages).to contain_exactly(
- "must be a valid group id",
+ expect(errors["/groups_property"].full_messages).to contain_exactly(
+ "must be an array of valid group ids",
)
end
- it "should return the right hash of error messages when value of property is not a valid id of a group record" do
+ it "should return the right hash of error messages when number of groups ids does not satisfy min or max validations" do
+ group_1 = Fabricate(:group)
+ group_2 = Fabricate(:group)
+ group_3 = Fabricate(:group)
+
schema = {
name: "section",
properties: {
group_property: {
- type: "group",
+ type: "groups",
+ validations: {
+ min: 1,
+ max: 2,
+ },
+ },
+ },
+ }
+
+ errors = described_class.new(schema: schema, object: { group_property: [] }).validate
+
+ expect(errors.keys).to eq(["/group_property"])
+
+ expect(errors["/group_property"].full_messages).to contain_exactly(
+ "must have at least 1 group ids",
+ )
+
+ errors =
+ described_class.new(
+ schema: schema,
+ object: {
+ group_property: [group_1.id, group_2.id, group_3.id],
+ },
+ ).validate
+
+ expect(errors.keys).to eq(["/group_property"])
+
+ expect(errors["/group_property"].full_messages).to contain_exactly(
+ "must have at most 2 group ids",
+ )
+ end
+
+ it "should return the right hash of error messages when value of property is an array containing invalid group ids" do
+ schema = {
+ name: "section",
+ properties: {
+ groups_property: {
+ type: "groups",
},
child_groups: {
type: "objects",
schema: {
name: "child_group",
properties: {
- group_property_2: {
- type: "group",
+ groups_property_2: {
+ type: "groups",
},
},
},
@@ -916,19 +957,19 @@ RSpec.describe ThemeSettingsObjectValidator do
described_class.new(
schema:,
object: {
- group_property: 99_999_999,
- child_groups: [{ group_property_2: 99_999_999 }],
+ groups_property: [99_999_999],
+ child_groups: [{ groups_property_2: [99_999_999] }],
},
).validate
- expect(errors.keys).to eq(%w[/group_property /child_groups/0/group_property_2])
+ expect(errors.keys).to eq(%w[/groups_property /child_groups/0/groups_property_2])
- expect(errors["/group_property"].full_messages).to contain_exactly(
- "must be a valid group id",
+ expect(errors["/groups_property"].full_messages).to contain_exactly(
+ "must be an array of valid group ids",
)
- expect(errors["/child_groups/0/group_property_2"].full_messages).to contain_exactly(
- "must be a valid group id",
+ expect(errors["/child_groups/0/groups_property_2"].full_messages).to contain_exactly(
+ "must be an array of valid group ids",
)
end