diff --git a/app/helpers/better_together/application_helper.rb b/app/helpers/better_together/application_helper.rb index 7470d9d88..d7d563334 100644 --- a/app/helpers/better_together/application_helper.rb +++ b/app/helpers/better_together/application_helper.rb @@ -160,6 +160,17 @@ def open_graph_meta_tags # rubocop:todo Metrics/AbcSize, Metrics/MethodLength, M # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity + # Generates `` tags for + # each locale supported by the application. These tags help search engines + # understand language-specific versions of a page. + def hreflang_links + tags = I18n.available_locales.map do |locale| + tag.link(rel: 'alternate', hreflang: locale, href: url_for(locale:, only_path: false)) + end + + safe_join(tags, "\n") + end + # Retrieves the setup wizard for hosts or raises an error if not found. # This is crucial for initial setup processes and should be pre-configured. def host_setup_wizard diff --git a/app/views/layouts/better_together/application.html.erb b/app/views/layouts/better_together/application.html.erb index 5a40356a7..caab338ba 100644 --- a/app/views/layouts/better_together/application.html.erb +++ b/app/views/layouts/better_together/application.html.erb @@ -17,6 +17,8 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> + <%= tag.link rel: 'canonical', href: url_for(only_path: false) %> + <%= content_for?(:hreflang_links) ? yield(:hreflang_links) : hreflang_links %> diff --git a/app/views/layouts/better_together/turbo_native.html.erb b/app/views/layouts/better_together/turbo_native.html.erb index 68f193a01..8edaee98b 100644 --- a/app/views/layouts/better_together/turbo_native.html.erb +++ b/app/views/layouts/better_together/turbo_native.html.erb @@ -15,8 +15,10 @@ <%= robots_meta_tag %> - <%= csrf_meta_tags %> - <%= csp_meta_tag %> + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + <%= tag.link rel: 'canonical', href: url_for(only_path: false) %> + <%= content_for?(:hreflang_links) ? yield(:hreflang_links) : hreflang_links %> diff --git a/spec/helpers/better_together/application_helper_spec.rb b/spec/helpers/better_together/application_helper_spec.rb index 735112991..30d0056e3 100644 --- a/spec/helpers/better_together/application_helper_spec.rb +++ b/spec/helpers/better_together/application_helper_spec.rb @@ -3,7 +3,22 @@ require 'rails_helper' module BetterTogether - RSpec.describe ApplicationHelper do + RSpec.describe ApplicationHelper, type: :helper do + + describe '#hreflang_links' do + it 'returns alternate link tags for all locales' do + allow(I18n).to receive(:available_locales).and_return(%i[en fr]) + allow(helper).to receive(:url_for) do |options| + "http://example.com/#{options[:locale]}" + end + + html = helper.hreflang_links + + expect(html).to include('rel="alternate" hreflang="en" href="http://example.com/en"') + expect(html).to include('rel="alternate" hreflang="fr" href="http://example.com/fr"') + end + end + describe '#robots_meta_tag' do it 'renders default robots meta tag' do # rubocop:todo RSpec/MultipleExpectations tag = helper.robots_meta_tag diff --git a/spec/views/layouts/better_together/application.html.erb_spec.rb b/spec/views/layouts/better_together/application.html.erb_spec.rb new file mode 100644 index 000000000..86e0303aa --- /dev/null +++ b/spec/views/layouts/better_together/application.html.erb_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'layouts/better_together/application', type: :view do + before do + view.extend BetterTogether::ApplicationHelper + + allow(view).to receive(:host_platform).and_return(double(name: 'Platform', cache_key_with_version: '1', css_block: nil)) + allow(view).to receive(:open_graph_meta_tags).and_return('') + allow(view).to receive(:seo_meta_tags).and_return('') + allow(view).to receive(:metrics_body_tag).and_yield + allow(view).to receive(:javascript_importmap_tags).and_return('') + allow(view).to receive(:stylesheet_link_tag).and_return('') + allow(view).to receive(:csrf_meta_tags).and_return('') + allow(view).to receive(:csp_meta_tag).and_return('') + allow(view).to receive(:javascript_i18n).and_return({ translations: {} }) + allow(view).to receive(:render).and_call_original + allow(view).to receive(:render).with('layouts/better_together/custom_head_javascript').and_return('') + allow(view).to receive(:render).with('layouts/better_together/custom_stylesheets').and_return('') + allow(view).to receive(:render).with('layouts/better_together/header').and_return('') + allow(view).to receive(:render).with('layouts/better_together/flash_messages').and_return('') + allow(view).to receive(:render).with('layouts/better_together/extra_page_content_bottom').and_return('') + allow(view).to receive(:render).with('layouts/better_together/footer').and_return('') + allow(view).to receive(:render).with('layouts/better_together/mobile_bar').and_return('') + allow(view).to receive(:render).with('layouts/better_together/custom_body_javascript').and_return('') + allow(view).to receive(:hreflang_links).and_return('') + allow(view).to receive(:url_for).with(only_path: false).and_return('http://test.host/current') + end + + it 'renders canonical and hreflang links by default' do + render template: 'layouts/better_together/application' + + expect(rendered).to match(%r{]+rel="canonical"[^>]+href="http://test.host/current"[^>]*>}) + expect(rendered).to include('') + end + + it 'renders custom hreflang links when content_for provided' do + view.content_for :hreflang_links, '' + + render template: 'layouts/better_together/application' + + expect(rendered).to include('') + expect(rendered).not_to include('') + end +end + diff --git a/spec/views/layouts/better_together/turbo_native.html.erb_spec.rb b/spec/views/layouts/better_together/turbo_native.html.erb_spec.rb new file mode 100644 index 000000000..f36d7f6bb --- /dev/null +++ b/spec/views/layouts/better_together/turbo_native.html.erb_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'layouts/better_together/turbo_native', type: :view do + before do + view.extend BetterTogether::ApplicationHelper + + allow(view).to receive(:host_platform).and_return(double(name: 'Platform', cache_key_with_version: '1', css_block: nil)) + allow(view).to receive(:open_graph_meta_tags).and_return('') + allow(view).to receive(:seo_meta_tags).and_return('') + allow(view).to receive(:metrics_body_tag).and_yield + allow(view).to receive(:javascript_importmap_tags).and_return('') + allow(view).to receive(:stylesheet_link_tag).and_return('') + allow(view).to receive(:csrf_meta_tags).and_return('') + allow(view).to receive(:csp_meta_tag).and_return('') + allow(view).to receive(:javascript_i18n).and_return({ translations: {} }) + allow(view).to receive(:render).and_call_original + allow(view).to receive(:render).with('layouts/better_together/custom_head_javascript').and_return('') + allow(view).to receive(:render).with('layouts/better_together/custom_stylesheets').and_return('') + allow(view).to receive(:render).with('layouts/better_together/header').and_return('') + allow(view).to receive(:render).with('layouts/better_together/flash_messages').and_return('') + allow(view).to receive(:render).with('layouts/better_together/extra_page_content_bottom').and_return('') + allow(view).to receive(:render).with('layouts/better_together/footer').and_return('') + allow(view).to receive(:render).with('layouts/better_together/custom_body_javascript').and_return('') + allow(view).to receive(:hreflang_links).and_return('') + allow(view).to receive(:url_for).with(only_path: false).and_return('http://test.host/current') + end + + it 'renders canonical and hreflang links by default' do + render template: 'layouts/better_together/turbo_native' + + expect(rendered).to match(%r{]+rel="canonical"[^>]+href="http://test.host/current"[^>]*>}) + expect(rendered).to include('') + end + + it 'renders custom hreflang links when content_for provided' do + view.content_for :hreflang_links, '' + + render template: 'layouts/better_together/turbo_native' + + expect(rendered).to include('') + expect(rendered).not_to include('') + end +end +