Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/components/admin/error_summary_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ def dotted_array_attribute?(error)
end

def format_dotted_attribute_message(error)
model_key = object.class.try(:model_name)&.i18n_key
attribute_label = error.attribute.to_s.split(".").map { |part|
part.match?(/\A\d+\z/) ? (part.to_i + 1).to_s : part.humanize.downcase
if part.match?(/\A\d+\z/)
(part.to_i + 1).to_s
else
i18n_key = "activerecord.attributes.#{model_key}.#{part}"
I18n.t(i18n_key, default: nil) || part.humanize.downcase
end
}.join(" ").capitalize
"#{attribute_label} #{error.message}"
end
Expand Down
12 changes: 11 additions & 1 deletion app/models/configurable_document_types/topical_event.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
}
]
},
"title": {
"title": "Title",
"block": "default_string",
"attribute_path": ["title"],
"translatable": true
},
"url": {
"title": "URL",
"block": "default_string",
Expand All @@ -130,6 +136,9 @@
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
Expand All @@ -156,7 +165,8 @@
"attributes": ["social_media_links"],
"fields": {
"service_field": "social_media_service_name",
"url_field": "url"
"url_field": "url",
"title_field": "title"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ def social_media_links(attribute)
return [] if content.blank?

content.map do |item|
# `item` looks something like `{"url"=>"foo", "social_media_service_name"=>"Facebook"}`
# `item` looks something like `{"url"=>"foo", "social_media_service_name"=>"Facebook", "title"=> "Optional title"}`
service_name = item["social_media_service_name"]
service_url = item["url"]
title = item["title"]
{
title: service_name,
title: title.presence || service_name,
service_type: service_name.parameterize, # "Google Plus" => "google-plus"
href: service_url,
}
Expand Down
30 changes: 25 additions & 5 deletions app/validators/social_media_links_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ def initialize(opts = {})
@attributes = opts[:attributes]
@channel_field = opts[:fields]["service_field"]
@url_field = opts[:fields]["url_field"]
@title_field = opts[:fields]["title_field"]

super
end

Expand All @@ -11,34 +13,52 @@ def validate(record)
arr = record.send(attribute_name.to_sym) || []
@channels_seen = []
@urls_seen = []
@titles_seen = []

arr.each_with_index do |social_media_account, index|
channel_name = social_media_account[@channel_field]
url = social_media_account[@url_field]
title = social_media_account["title"]
channel = { channel_name: channel_name, title: title }

validate_social_media_channel(channel_name, index, record, attribute_name)
validate_social_media_title(title, index, record, attribute_name)
validate_social_media_url(url, index, record, attribute_name)
validate_social_media_channel(channel, index, record, attribute_name)
end
end
end

private

def validate_social_media_channel(channel_name, index, record, attribute_name)
if channel_name.blank?
def validate_social_media_channel(channel, index, record, attribute_name)
if channel[:channel_name].blank?
record.errors.add(
:"#{attribute_name}.#{index}.#{@channel_field}",
:blank,
message: "cannot be blank",
)
elsif channel_name != "Other" && @channels_seen.include?(channel_name)
elsif @channels_seen.select { |c| c[:channel_name] == channel[:channel_name] && c[:title] == channel[:title] && c[:title].blank? }.any?
record.errors.add(
:"#{attribute_name}.#{index}.#{@channel_field}",
:taken,
message: "must be unique",
)
else
@channels_seen << channel_name
@channels_seen << channel
end
end

def validate_social_media_title(title, index, record, attribute_name)
return if title.blank?

if @titles_seen.include?(title)
record.errors.add(
:"#{attribute_name}.#{index}.#{@title_field}",
:taken,
message: "must be unique",
)
else
@titles_seen << title
end
end

Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ en:
edition:
html_attachments: HTML attachments
nation_inapplicabilities: Excluded nations
standard_edition:
social_media_links: Social media account
social_media_service_name: channel
image_data:
file: Image
policy_group/policy_group_attachments/attachment/attachment_data:
Expand Down
9 changes: 9 additions & 0 deletions features/fixtures/test_configurable_document_type.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@
"block": "default_string",
"attribute_path": ["url"],
"translatable": true
},
"title": {
"title": "Title",
"block": "default_string",
"attribute_path": ["title"],
"translatable": true
}
}
}
Expand Down Expand Up @@ -184,6 +190,9 @@
},
"url": {
"type": "string"
},
"title": {
"type": "string"
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion test/components/admin/error_summary_component_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class Admin::ErrorSummaryComponentTest < ViewComponent::TestCase
assert_equal third_link[:href], "#labelled_error_summary_test_object_date"
end

test "renders full_message for dotted attributes" do
test "renders full message for dotted attributes" do
object = DottedAttributeErrorSummaryTestObject.new("title", Time.zone.today)
object.errors.add("social_media_links.0.url".to_sym, :blank, message: "cannot be blank")
render_inline(Admin::ErrorSummaryComponent.new(object:))
Expand All @@ -163,6 +163,18 @@ class Admin::ErrorSummaryComponentTest < ViewComponent::TestCase
assert_equal "Social media links 1 url cannot be blank", link.text
assert_equal "#dotted_attribute_error_summary_test_object_social_media_links_0_url", link[:href]
end

test "renders full message with locale labels for dotted attributes" do
object = DottedAttributeErrorSummaryTestObject.new("title", Time.zone.today)
object.class.model_name.define_singleton_method(:i18n_key) { :standard_edition }
object.errors.add("social_media_links.0.url".to_sym, :blank, message: "cannot be blank")
object.errors.add("social_media_links.0.social_media_service_name".to_sym, :blank, message: "cannot be blank")
render_inline(Admin::ErrorSummaryComponent.new(object:))

links = page.find_all(".gem-c-error-summary__list-item a")
assert_equal "Social media account 1 url cannot be blank", links[0].text
assert_equal "Social media account 1 channel cannot be blank", links[1].text
end
end

class ErrorSummaryTestObject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,78 @@ class PublishingApi::PayloadBuilder::BlockContentTest < ActiveSupport::TestCase
end

test "social_media_links returns array of social media links" do
value_of_links = [
{ "social_media_service_name" => "Twitter", "url" => "https://twitter.com" },
{ "social_media_service_name" => "Other", "title" => "Other 1", "url" => "https://example.com" },
{ "social_media_service_name" => "Other", "title" => "Other 2", "url" => "https://personal.com" },
]
@block_content.stubs(:some_attribute).returns(value_of_links)

builder = PublishingApi::PayloadBuilder::BlockContent.new(@item)

expected_payload = [
{
title: "Twitter",
service_type: "twitter",
href: "https://twitter.com",
},
{
title: "Other 1",
service_type: "other",
href: "https://example.com",
},
{
title: "Other 2",
service_type: "other",
href: "https://personal.com",
},
]
assert_equal expected_payload, builder.send(:social_media_links, :some_attribute)
end

test "social_media_links defaults title to service name when no title provided" do
value_of_links = [{ "social_media_service_name" => "twitter", "url" => "https://example.com" }]
@block_content.stubs(:some_attribute).returns(value_of_links)

builder = PublishingApi::PayloadBuilder::BlockContent.new(@item)

expected_payload = [
{
title: value_of_links.first["social_media_service_name"],
service_type: value_of_links.first["social_media_service_name"].parameterize,
href: value_of_links.first["url"],
title: "twitter",
service_type: "twitter",
href: "https://example.com",
},
]
assert_equal expected_payload, builder.send(:social_media_links, :some_attribute)
end

test "social_media_links uses custom title when provided" do
value_of_links = [{ "social_media_service_name" => "Twitter", "url" => "https://twitter.com/govuk", "title" => "GOV.UK on Twitter" }]
@block_content.stubs(:some_attribute).returns(value_of_links)

builder = PublishingApi::PayloadBuilder::BlockContent.new(@item)

expected_payload = [
{
title: "GOV.UK on Twitter",
service_type: "twitter",
href: "https://twitter.com/govuk",
},
]
assert_equal expected_payload, builder.send(:social_media_links, :some_attribute)
end

test "social_media_links falls back to service name when title is blank" do
value_of_links = [{ "social_media_service_name" => "Facebook", "url" => "https://facebook.com/govuk", "title" => "" }]
@block_content.stubs(:some_attribute).returns(value_of_links)

builder = PublishingApi::PayloadBuilder::BlockContent.new(@item)

expected_payload = [
{
title: "Facebook",
service_type: "facebook",
href: "https://facebook.com/govuk",
},
]
assert_equal expected_payload, builder.send(:social_media_links, :some_attribute)
Expand Down
83 changes: 70 additions & 13 deletions test/unit/app/validators/social_media_links_validator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class SocialMediaLinksValidatorTest < ActiveSupport::TestCase
fields: {
"service_field" => "social_media_service_name",
"url_field" => "url",
"title_field" => "title",
},
})
end
Expand Down Expand Up @@ -78,46 +79,102 @@ class SocialMediaLinksValidatorTestClass
assert_includes block_content.errors["social_media_links.0.url".to_sym], "is invalid - use the full URL, including https://"
end

test "social media links are invalid if two of the same channel are provided" do
test "social media links are invalid if two channels have the same URL" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Twitter", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govukpage2" },
]
@validator.validate(block_content)

assert_empty block_content.errors["social_media_links.0.social_media_service_name".to_sym]
assert_includes block_content.errors["social_media_links.1.social_media_service_name".to_sym], "must be unique"
assert_empty block_content.errors["social_media_links.0.url".to_sym]
assert_includes block_content.errors["social_media_links.1.url".to_sym], "must be unique"
end

test "social media links are invalid if two channels have the same URL" do
test "social media links are valid when a channel is chosen and a well-formed URL is provided" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Twitter", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "LinkedIn", "url" => "https://linkedin.com/company/govuk" },
]
@validator.validate(block_content)

assert_empty block_content.errors["social_media_links.0.url".to_sym]
assert_includes block_content.errors["social_media_links.1.url".to_sym], "must be unique"
assert block_content.errors.empty?
end

test "social media links are valid when multiple 'Other' channels are provided with different URLs" do
test "social media links are valid when titles are unique" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Other", "url" => "http://example.com/one" },
{ "social_media_service_name" => "Other", "url" => "http://example.com/two" },
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govuk", "title" => "GOV.UK on Facebook" },
{ "social_media_service_name" => "Twitter", "url" => "http://twitter.com/govuk", "title" => "GOV.UK on Twitter" },
]
@validator.validate(block_content)

assert block_content.errors.empty?
end

test "social media links are valid when a channel is chosen and a well-formed URL is provided" do
test "social media links are valid when titles are blank" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govuk", "title" => "" },
{ "social_media_service_name" => "Twitter", "url" => "http://twitter.com/govuk", "title" => "" },
]
@validator.validate(block_content)

assert block_content.errors.empty?
end

test "social media links are invalid when any two accounts have the same title" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Facebook", "title" => "Our updates", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "Twitter", "title" => "Our updates", "url" => "http://twitter.com/govuk" },
]
@validator.validate(block_content)

assert_includes block_content.errors["social_media_links.1.title".to_sym], "must be unique"
end

test "social media links are invalid when multiple instances of the same channel are provided, with no distinct titles" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "LinkedIn", "url" => "https://linkedin.com/company/govuk" },
{ "social_media_service_name" => "Facebook", "url" => "http://facebook.com/govukpage2" },
]
@validator.validate(block_content)

assert_empty block_content.errors["social_media_links.0.social_media_service_name".to_sym]
assert_includes block_content.errors["social_media_links.1.social_media_service_name".to_sym], "must be unique"
end

test "social media links are invalid when multiple 'Other' channels are provided, with no distinct titles" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Other", "url" => "http://example.com/one" },
{ "social_media_service_name" => "Other", "url" => "http://example.com/two" },
]
@validator.validate(block_content)

assert_includes block_content.errors["social_media_links.1.social_media_service_name".to_sym], "must be unique"
end

test "social media links are invalid and only display title uniqueness error, if multiple instances of the same channel are provided, and their titles match" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Facebook", "title" => "Facebook", "url" => "http://facebook.com/govuk" },
{ "social_media_service_name" => "Facebook", "title" => "Facebook", "url" => "http://facebook.com/govukpage2" },
]
@validator.validate(block_content)

assert_includes block_content.errors["social_media_links.1.title".to_sym], "must be unique"
assert block_content.errors["social_media_links.1.social_media_service_name".to_sym].empty?
end

test "social media links are valid when multiple instances of the same channel are provided with different titles and different URLs" do
block_content = SocialMediaLinksValidatorTestClass.new
block_content.social_media_links = [
{ "social_media_service_name" => "Facebook", "title" => "Facebook Corporate", "url" => "http://example.com/one" },
{ "social_media_service_name" => "Facebook", "title" => "Facebook Fun", "url" => "http://example.com/two" },
]
@validator.validate(block_content)

Expand Down