From 7ceda857b2c678057e8cbc3cfb02a4bd309fcb7c Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sun, 12 Apr 2026 12:21:58 -1000 Subject: [PATCH] Extract SERP chrome into separate React mounts --- app/helpers/react_helper.rb | 88 +++++++---- .../components/SearchResultsFooter.tsx | 39 +++++ .../components/SearchResultsHeader.tsx | 53 +++++++ .../components/SearchResultsLayout.tsx | 29 +--- .../test/SearchResultsFooter.test.tsx | 55 +++++++ .../test/SearchResultsHeader.test.tsx | 70 +++++++++ .../test/SearchResultsLayout.test.tsx | 31 ++-- .../SearchResultsLayout.test.tsx.snap | 145 ++++++------------ spec/helpers/react_helper_spec.rb | 19 ++- 9 files changed, 354 insertions(+), 175 deletions(-) create mode 100644 app/javascript/components/SearchResultsFooter.tsx create mode 100644 app/javascript/components/SearchResultsHeader.tsx create mode 100644 app/javascript/test/SearchResultsFooter.test.tsx create mode 100644 app/javascript/test/SearchResultsHeader.test.tsx diff --git a/app/helpers/react_helper.rb b/app/helpers/react_helper.rb index 45be4b518..2f523a3f9 100644 --- a/app/helpers/react_helper.rb +++ b/app/helpers/react_helper.rb @@ -2,34 +2,23 @@ module ReactHelper def search_results_layout(search, params, vertical, affiliate, search_options) - data = { - additionalResults: govbox_set_data(search), - affiliate: affiliate_data(affiliate), - agencyName: agency_name(affiliate.agency), - alert: search_page_alert(affiliate.alert), - extendedHeader: affiliate.use_extended_header, - facetsEnabled: ENV.fetch('FACETED_SEARCH_ENABLED', 'false') == 'true', - fontsAndColors: affiliate.visual_design_json, - footerLinks: links(affiliate, :footer_links), - jobsEnabled: (affiliate.jobs_enabled? and search.modules.include?('JOBS')), - language: affiliate.language.slice(:code, :rtl), - navigationLinks: navigation_links(search, params), - noResultsMessage: no_result_message(search), - page: page_data(affiliate), - params:, - primaryHeaderLinks: links(affiliate, :primary_header_links), - relatedSearches: related_searches(search), - relatedSites: related_sites(search), - relatedSitesDropdownLabel: affiliate.related_sites_dropdown_label, - resultsData: results_data(search), - secondaryHeaderLinks: links(affiliate, :secondary_header_links), - sitelimit: sitelimit_alert(search, params), - spellingSuggestion: spelling_text(search, search_options), - translations: translations(affiliate.locale), - vertical: - } + safe_join([ + search_results_header(affiliate), + search_results_main(search, params, vertical, affiliate, search_options), + search_results_footer(affiliate) + ]) + end - react_component('SearchResultsLayout', data.compact_blank) + def search_results_header(affiliate) + react_component('SearchResultsHeader', search_results_header_props(affiliate).compact_blank) + end + + def search_results_main(search, params, vertical, affiliate, search_options) + react_component('SearchResultsLayout', search_results_layout_props(search, params, vertical, affiliate, search_options).compact_blank) + end + + def search_results_footer(affiliate) + react_component('SearchResultsFooter', search_results_footer_props(affiliate).compact_blank) end def affiliate_data(affiliate) @@ -64,6 +53,51 @@ def logo_text(blob) private + def search_results_header_props(affiliate) + search_results_context_props(affiliate).merge( + extendedHeader: affiliate.use_extended_header, + page: page_data(affiliate), + primaryHeaderLinks: links(affiliate, :primary_header_links), + secondaryHeaderLinks: links(affiliate, :secondary_header_links) + ) + end + + def search_results_layout_props(search, params, vertical, affiliate, search_options) + search_results_context_props(affiliate).merge( + additionalResults: govbox_set_data(search), + affiliate: affiliate_data(affiliate), + agencyName: agency_name(affiliate.agency), + alert: search_page_alert(affiliate.alert), + facetsEnabled: ENV.fetch('FACETED_SEARCH_ENABLED', 'false') == 'true', + jobsEnabled: (affiliate.jobs_enabled? and search.modules.include?('JOBS')), + navigationLinks: navigation_links(search, params), + noResultsMessage: no_result_message(search), + page: page_data(affiliate), + params:, + relatedSearches: related_searches(search), + relatedSites: related_sites(search), + relatedSitesDropdownLabel: affiliate.related_sites_dropdown_label, + resultsData: results_data(search), + sitelimit: sitelimit_alert(search, params), + spellingSuggestion: spelling_text(search, search_options), + vertical: + ) + end + + def search_results_footer_props(affiliate) + search_results_context_props(affiliate).merge( + footerLinks: links(affiliate, :footer_links) + ) + end + + def search_results_context_props(affiliate) + { + fontsAndColors: affiliate.visual_design_json, + language: affiliate.language.slice(:code, :rtl), + translations: translations(affiliate.locale) + } + end + def related_searches(search) return [] if search.related_search.nil? diff --git a/app/javascript/components/SearchResultsFooter.tsx b/app/javascript/components/SearchResultsFooter.tsx new file mode 100644 index 000000000..d129ed298 --- /dev/null +++ b/app/javascript/components/SearchResultsFooter.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { I18n } from 'i18n-js'; + +import { Footer } from './Footer/Footer'; +import { LanguageContext } from '../contexts/LanguageContext'; +import { StyleContext, styles } from '../contexts/StyleContext'; +import { FontsAndColors, Language } from './SearchResultsLayout'; + +interface SearchResultsFooterProps { + footerLinks?: { + title: string; + url: string; + }[]; + translations: Record; + language?: Language; + fontsAndColors?: FontsAndColors; +} + +const SearchResultsFooter = ({ + footerLinks, + translations, + language = { code: 'en', rtl: false }, + fontsAndColors +}: SearchResultsFooterProps) => { + const i18n = new I18n(translations); + i18n.defaultLocale = 'en'; + i18n.enableFallback = true; + i18n.locale = language.code; + + return ( + + +