├── .dockerignore ├── .erb_lint.yml ├── .github ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── actionlint.yml │ ├── ci.yml │ ├── copy-pr-template-to-dependabot-prs.yml │ ├── deploy.yml │ ├── jasmine.yml │ ├── pact-verify.yml │ ├── release.yml │ └── rspec.yml ├── .gitignore ├── .govuk_dependabot_merger.yml ├── .nvmrc ├── .nycrc ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .yarn └── releases │ └── yarn-3.5.0.cjs ├── .yarnrc.yml ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENCE.txt ├── Procfile.dev ├── README.md ├── Rakefile ├── app.json ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── calendars │ │ │ ├── bunting-grey-string.svg │ │ │ └── festive-lights-tinsel.svg │ │ ├── campaign │ │ │ ├── britain_is_great │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ │ ├── custom-logos │ │ │ │ └── environment-agency.png │ │ │ ├── dvla-new-licence-rules │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ ├── 960px.jpg │ │ │ │ └── INF45x3W.pdf │ │ │ ├── dwp-workplace-pensions │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ │ ├── fire-kills │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ │ ├── know-before-you-go │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ │ ├── royal_mail_shares │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ │ ├── sort-my-tax │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ │ ├── uk-welcomes │ │ │ │ └── eugo-logo.png │ │ │ └── unimoney │ │ │ │ ├── 320px.jpg │ │ │ │ ├── 768px.jpg │ │ │ │ └── 960px.jpg │ │ ├── components │ │ │ └── download-link │ │ │ │ ├── calendar-icon.svg │ │ │ │ └── download-icon.svg │ │ ├── getsatisfaction.jpg │ │ ├── homepage │ │ │ ├── cost-of-living-featured.png │ │ │ ├── find-a-job.png │ │ │ └── national-insurance-featured.png │ │ ├── landing_page │ │ │ ├── logo-nhs.svg │ │ │ └── placeholder │ │ │ │ ├── 960x640.png │ │ │ │ ├── chart.png │ │ │ │ ├── desktop.png │ │ │ │ ├── desktop_2x.png │ │ │ │ ├── landing_page_image.png │ │ │ │ ├── missions_featured_desktop.jpg │ │ │ │ ├── missions_featured_desktop_2x.jpg │ │ │ │ ├── missions_featured_mobile.jpg │ │ │ │ ├── missions_featured_mobile_2x.jpg │ │ │ │ ├── missions_featured_tablet.jpg │ │ │ │ ├── missions_featured_tablet_2x.jpg │ │ │ │ ├── missions_hero_desktop.jpg │ │ │ │ ├── missions_hero_desktop_2x.jpg │ │ │ │ ├── missions_hero_mobile.jpg │ │ │ │ ├── missions_hero_mobile_2x.jpg │ │ │ │ ├── missions_hero_tablet.jpg │ │ │ │ ├── missions_hero_tablet_2x.jpg │ │ │ │ ├── mobile.png │ │ │ │ ├── mobile_2x.png │ │ │ │ ├── tablet.png │ │ │ │ └── tablet_2x.png │ │ ├── ministry-of-defence-crest.png │ │ ├── roadmap │ │ │ ├── image.png │ │ │ ├── updates-blog.jpg │ │ │ ├── updates-goals.jpg │ │ │ └── updates-work.jpg │ │ ├── specialist-documents │ │ │ └── protected-food-drink-names │ │ │ │ ├── protected-designation-of-origin-pdo.png │ │ │ │ ├── protected-geographical-indication-pgi.png │ │ │ │ └── traditional-speciality-guaranteed-tsg.png │ │ ├── start-pages │ │ │ └── make-a-sorn │ │ │ │ ├── performance-icon-2x.png │ │ │ │ └── performance-icon.png │ │ ├── templogo.png │ │ └── travel-advice │ │ │ ├── feed-icon-black.png │ │ │ ├── mail-icon-x2.png │ │ │ └── mail-icon.png │ ├── javascripts │ │ ├── application.js │ │ ├── dependencies.js │ │ ├── main.js │ │ ├── modules │ │ │ ├── main-navigation.js │ │ │ ├── sticky-element-container.js │ │ │ └── transaction-survey-form.js │ │ ├── static-error-pages.js │ │ ├── support.js │ │ ├── test-dependencies.js │ │ └── views │ │ │ └── travel-advice.js │ └── stylesheets │ │ ├── application.scss │ │ ├── components │ │ ├── _calendar.scss │ │ └── _download-link.scss │ │ ├── helpers │ │ ├── _content-bottom-margin.scss │ │ ├── _inverse-background.scss │ │ ├── _parts.scss │ │ ├── _sticky-element-container.scss │ │ └── _truncated-url.scss │ │ ├── mixins │ │ └── _margins.scss │ │ ├── static-error-pages.scss │ │ └── views │ │ ├── _calendars.scss │ │ ├── _cookie-settings.scss │ │ ├── _csv_preview.scss │ │ ├── _homepage.scss │ │ ├── _homepage_header.scss │ │ ├── _homepage_more_on_govuk.scss │ │ ├── _landing_page.scss │ │ ├── _landing_page │ │ ├── block-error.scss │ │ ├── box.scss │ │ ├── card.scss │ │ ├── columns_layout.scss │ │ ├── featured.scss │ │ ├── helpers │ │ │ ├── _backgrounds.scss │ │ │ ├── _borders.scss │ │ │ └── _colours.scss │ │ ├── hero.scss │ │ ├── image.scss │ │ ├── logo.scss │ │ ├── main-navigation.scss │ │ ├── quote.scss │ │ ├── side-navigation.scss │ │ └── themes │ │ │ └── prime-ministers-office-10-downing-street.scss │ │ ├── _local-transaction.scss │ │ ├── _location_form.scss │ │ ├── _place-list.scss │ │ ├── _popular_links.scss │ │ ├── _published-dates-button-group.scss │ │ ├── _publisher_metadata.scss │ │ ├── _roadmap.scss │ │ ├── _service-toolkit.scss │ │ ├── _sidebar-navigation.scss │ │ ├── _specialist-document.scss │ │ └── _travel-advice.scss ├── controllers │ ├── account_home_controller.rb │ ├── answer_controller.rb │ ├── api │ │ └── local_authority_controller.rb │ ├── application_controller.rb │ ├── calendar_controller.rb │ ├── case_study_controller.rb │ ├── concerns │ │ ├── bank_hol_ab_testable.rb │ │ ├── cacheable.rb │ │ ├── previewable.rb │ │ └── split_postcode_support.rb │ ├── content_items_controller.rb │ ├── corporate_information_page_controller.rb │ ├── csv_preview_controller.rb │ ├── development_controller.rb │ ├── electoral_controller.rb │ ├── error_controller.rb │ ├── fatality_notice_controller.rb │ ├── favicon_controller.rb │ ├── field_of_operation_controller.rb │ ├── fields_of_operation_controller.rb │ ├── find_local_council_controller.rb │ ├── flexible_page_controller.rb │ ├── get_involved_controller.rb │ ├── help_controller.rb │ ├── help_page_controller.rb │ ├── homepage_controller.rb │ ├── landing_page_controller.rb │ ├── licence_transaction_controller.rb │ ├── local_transaction_controller.rb │ ├── news_article_controller.rb │ ├── place_controller.rb │ ├── placeholder_controller.rb │ ├── random_controller.rb │ ├── roadmap_controller.rb │ ├── service_manual_controller.rb │ ├── service_toolkit_controller.rb │ ├── sessions_controller.rb │ ├── simple_smart_answers_controller.rb │ ├── specialist_document_controller.rb │ ├── speech_controller.rb │ ├── static_error_pages_controller.rb │ ├── take_part_controller.rb │ ├── transaction_controller.rb │ └── travel_advice_controller.rb ├── helpers │ ├── application_helper.rb │ ├── block_helper.rb │ ├── calendar_helper.rb │ ├── contents_list_helper.rb │ ├── currency_helper.rb │ ├── date_helper.rb │ ├── draft_helper.rb │ ├── error_items_helper.rb │ ├── govuk_personalisation_helper.rb │ ├── link_helper.rb │ ├── locale_helper.rb │ ├── location_form_helper.rb │ ├── parts_navigation_helper.rb │ ├── phone_number_helper.rb │ ├── theme_type_helper.rb │ ├── travel_advice_helper.rb │ └── url_helper.rb ├── models │ ├── calendar.rb │ ├── calendar │ │ ├── division.rb │ │ ├── event.rb │ │ └── year.rb │ ├── case_study.rb │ ├── concerns │ │ ├── emphasised_organisations.rb │ │ ├── news_image.rb │ │ ├── parts.rb │ │ ├── people.rb │ │ ├── political.rb │ │ ├── single_page_notification_button.rb │ │ ├── updatable.rb │ │ ├── withdrawable.rb │ │ └── worldwide_organisations.rb │ ├── content_item.rb │ ├── content_item_factory.rb │ ├── contents_outline.rb │ ├── corporate_information_page.rb │ ├── detailed_guide.rb │ ├── election_postcode.rb │ ├── fatality_notice.rb │ ├── field_of_operation.rb │ ├── fields_of_operation.rb │ ├── finder.rb │ ├── flexible_page.rb │ ├── flexible_page │ │ ├── flexible_section │ │ │ ├── base.rb │ │ │ ├── page_title.rb │ │ │ └── rich_content.rb │ │ └── flexible_section_factory.rb │ ├── get_involved.rb │ ├── homepage.rb │ ├── landing_page.rb │ ├── landing_page │ │ ├── block │ │ │ ├── action_link.rb │ │ │ ├── base.rb │ │ │ ├── block_error.rb │ │ │ ├── blocks_container.rb │ │ │ ├── box.rb │ │ │ ├── card.rb │ │ │ ├── columns_layout.rb │ │ │ ├── document_list.rb │ │ │ ├── featured.rb │ │ │ ├── govspeak.rb │ │ │ ├── heading.rb │ │ │ ├── hero.rb │ │ │ ├── image.rb │ │ │ ├── layout_base.rb │ │ │ ├── logo.rb │ │ │ ├── main_navigation.rb │ │ │ ├── quote.rb │ │ │ ├── share_links.rb │ │ │ ├── side_navigation.rb │ │ │ └── two_column_layout.rb │ │ └── block_factory.rb │ ├── licence_transaction.rb │ ├── local_authority.rb │ ├── local_transaction.rb │ ├── location_error.rb │ ├── locations_api_postcode_response.rb │ ├── logo.rb │ ├── news_article.rb │ ├── organisation.rb │ ├── place.rb │ ├── places_manager_response.rb │ ├── postcode_lookup.rb │ ├── record_not_found.rb │ ├── service_manual_homepage.rb │ ├── service_manual_service_toolkit.rb │ ├── simple_smart_answer.rb │ ├── specialist_document.rb │ ├── speech.rb │ ├── transaction.rb │ ├── travel_advice.rb │ └── uprn.rb ├── presenters │ ├── address_list_presenter.rb │ ├── content_item_presenter.rb │ ├── contents_outline_presenter.rb │ ├── corporate_information_page_presenter.rb │ ├── electoral_presenter.rb │ ├── faq_presenter.rb │ ├── field_of_operation_presenter.rb │ ├── get_involved_presenter.rb │ ├── licence_details_presenter.rb │ ├── local_authority_presenter.rb │ ├── place_presenter.rb │ ├── service_manual_homepage_presenter.rb │ ├── simple_smart_answer_presenter.rb │ ├── specialist_document_presenter.rb │ ├── speech_presenter.rb │ ├── transaction_presenter.rb │ ├── travel_advice_index_presenter.rb │ └── travel_advice_presenter.rb ├── queries │ └── graphql │ │ └── edition_query.rb ├── services │ ├── csv_preview_service.rb │ └── electoral_service.rb └── views │ ├── answer │ └── show.html.erb │ ├── application │ ├── _draft_fields.html.erb │ └── _location_form.html.erb │ ├── calendar │ ├── _calendar_footer.html.erb │ ├── _calendar_head.html.erb │ ├── bank_holidays.html.erb │ └── when_do_the_clocks_change.html.erb │ ├── case_study │ └── show.html.erb │ ├── components │ ├── _calendar.html.erb │ ├── _download_link.html.erb │ └── docs │ │ ├── calendar.yml │ │ └── download_link.yml │ ├── corporate_information_page │ └── show.html.erb │ ├── csv_preview │ ├── access_limited.html.erb │ ├── malformed_csv.html.erb │ └── show.html.erb │ ├── development │ └── index.html.erb │ ├── electoral │ ├── _contact_details.html.erb │ ├── address_picker.html.erb │ └── results.html.erb │ ├── fatality_notice │ └── show.html.erb │ ├── field_of_operation │ └── show.html.erb │ ├── fields_of_operation │ └── index.html.erb │ ├── find_local_council │ ├── _base_page.html.erb │ ├── district_and_county_council.html.erb │ ├── index.html.erb │ ├── multiple_authorities.html.erb │ └── one_council.html.erb │ ├── flexible_page │ ├── flexible_sections │ │ ├── _page_title.html.erb │ │ └── _rich_content.html.erb │ └── show.html.erb │ ├── get_involved │ └── show.html.erb │ ├── help │ ├── ab_testing.html.erb │ ├── cookie_settings.html.erb │ ├── index.html.erb │ └── sign_in.html.erb │ ├── help_page │ └── show.html.erb │ ├── homepage │ ├── _government_activity.html.erb │ ├── _homepage_header.html.erb │ ├── _more_on_govuk.html.erb │ ├── _popular_links.html.erb │ ├── _promotion_slots.html.erb │ ├── _services_and_information.html.erb │ └── index.html.erb │ ├── landing_page │ ├── blocks │ │ ├── _action_link.html.erb │ │ ├── _block_error.html.erb │ │ ├── _blocks_container.html.erb │ │ ├── _box.html.erb │ │ ├── _card.html.erb │ │ ├── _columns_layout.html.erb │ │ ├── _document_list.html.erb │ │ ├── _featured.html.erb │ │ ├── _govspeak.html.erb │ │ ├── _heading.html.erb │ │ ├── _hero.html.erb │ │ ├── _image.html.erb │ │ ├── _logo.html.erb │ │ ├── _main_navigation.html.erb │ │ ├── _quote.html.erb │ │ ├── _share_links.html.erb │ │ ├── _side_navigation.html.erb │ │ └── _two_column_layout.html.erb │ ├── show.html.erb │ └── themes │ │ ├── _default.html.erb │ │ └── _prime-ministers-office-10-downing-street.html.erb │ ├── layouts │ └── application.html.erb │ ├── licence_transaction │ ├── _authority_base.html.erb │ ├── _authority_url.html.erb │ ├── _licensify_unavailable.html.erb │ ├── authority.html.erb │ ├── authority_interaction.html.erb │ ├── continues_on.html.erb │ ├── licence_not_found.html.erb │ ├── multiple_authorities.html.erb │ └── start.html.erb │ ├── local_transaction │ ├── _no_local_authority_url.html.erb │ ├── devolved_administration_service.html.erb │ ├── index.html.erb │ ├── multiple_authorities.html.erb │ ├── results.html.erb │ └── unavailable_service.html.erb │ ├── news_article │ └── show.html.erb │ ├── place │ ├── _place.html.erb │ ├── multiple_authorities.html.erb │ └── show.html.erb │ ├── placeholder │ └── show.html.erb │ ├── roadmap │ ├── _image.html.erb │ └── index.html.erb │ ├── service_manual │ └── index.html.erb │ ├── service_toolkit │ └── index.html.erb │ ├── shared │ ├── _base_page.html.erb │ ├── _body_with_related_links.html.erb │ ├── _email_subscribe_unsubscribe_flash.html.erb │ ├── _footer_navigation.html.erb │ ├── _history_notice.html.erb │ ├── _publication_metadata.html.erb │ ├── _published_dates_with_notification_button.html.erb │ ├── _publisher_metadata.html.erb │ ├── _sidebar_navigation.html.erb │ ├── _single_page_notification_button.html.erb │ └── _translations.html.erb │ ├── simple_smart_answers │ ├── _current_question.html.erb │ ├── _outcome.html.erb │ ├── flow.html.erb │ └── show.html.erb │ ├── specialist_document │ └── show.html.erb │ ├── speech │ └── show.html.erb │ ├── static_error_pages │ ├── 400.html.erb │ ├── 401.html.erb │ ├── 403.html.erb │ ├── 404.html.erb │ ├── 405.html.erb │ ├── 406.html.erb │ ├── 410.html.erb │ ├── 422.html.erb │ ├── 429.html.erb │ ├── 500.html.erb │ ├── 501.html.erb │ ├── 502.html.erb │ ├── 503.html.erb │ ├── 504.html.erb │ └── _error_page.html.erb │ ├── take_part │ └── show.html.erb │ ├── transaction │ ├── _additional_information_single.html.erb │ ├── _additional_information_tabbed.html.erb │ └── show.html.erb │ └── travel_advice │ ├── _country.atom.builder │ ├── _first_part.html.erb │ ├── index.atom.builder │ ├── index.html.erb │ ├── show.atom.builder │ ├── show.html+print.erb │ └── show.html.erb ├── bin ├── brakeman ├── bundle ├── dev ├── rails ├── rake ├── rubocop ├── setup ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── brakeman.ignore ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── govuk_examples.yml ├── i18n-tasks.yml ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── dartsass.rb │ ├── date_formats.rb │ ├── filter_parameter_logging.rb │ ├── govuk_publishing_components.rb │ ├── inflections.rb │ ├── local_links_manager.rb │ ├── locations_api.rb │ ├── mime_types.rb │ ├── permissions_policy.rb │ ├── places_manager.rb │ ├── prometheus.rb │ ├── session_store.rb │ ├── slimmer.rb │ ├── website_root.rb │ └── wrap_parameters.rb ├── locales │ ├── ar.yml │ ├── az.yml │ ├── be.yml │ ├── bg.yml │ ├── bn.yml │ ├── cs.yml │ ├── cy.yml │ ├── da.yml │ ├── de.yml │ ├── dr.yml │ ├── el.yml │ ├── en.yml │ ├── es-419.yml │ ├── es.yml │ ├── et.yml │ ├── fa.yml │ ├── fi.yml │ ├── fr.yml │ ├── gd.yml │ ├── gu.yml │ ├── he.yml │ ├── hi.yml │ ├── hr.yml │ ├── hu.yml │ ├── hy.yml │ ├── id.yml │ ├── is.yml │ ├── it.yml │ ├── ja.yml │ ├── ka.yml │ ├── kk.yml │ ├── ko.yml │ ├── ky.yml │ ├── lt.yml │ ├── lv.yml │ ├── ms.yml │ ├── mt.yml │ ├── ne.yml │ ├── nl.yml │ ├── no.yml │ ├── pa-pk.yml │ ├── pa.yml │ ├── pl.yml │ ├── ps.yml │ ├── pt.yml │ ├── ro.yml │ ├── ru.yml │ ├── si.yml │ ├── sk.yml │ ├── sl.yml │ ├── so.yml │ ├── sq.yml │ ├── sr.yml │ ├── sv.yml │ ├── sw.yml │ ├── ta.yml │ ├── th.yml │ ├── tk.yml │ ├── tr.yml │ ├── uk.yml │ ├── ur.yml │ ├── uz.yml │ ├── vi.yml │ ├── yi.yml │ ├── zh-hk.yml │ ├── zh-tw.yml │ └── zh.yml ├── puma.rb ├── routes.rb ├── secrets.yml └── sidekiq.yml ├── docs ├── adr │ ├── 0001-render-csv-previews-in-frontend.md │ └── 0002-flexible-landing-pages.md ├── building_blocks_for_flexible_content.md ├── calendars.md ├── elections-api.md ├── images │ └── homepage-promotion-slots.png ├── local-authorities-api.md ├── local-content-items.md └── update-homepage-promotion-slots.md ├── lib ├── api_error_routing_constraint.rb ├── content_item_loader.rb ├── data │ ├── bank-holidays.json │ ├── local-content-items │ │ └── test │ │ │ └── flexible-page.yml │ ├── specialist_documents │ │ └── protected_food_drink_name │ │ │ └── images.yaml │ └── when-do-the-clocks-change.json ├── format_routing_constraint.rb ├── frontend.rb ├── full_path_format_routing_constraint.rb ├── ics_renderer.rb ├── postcode_sanitizer.rb ├── sanitiser │ └── strategy.rb ├── simple_smart_answers │ ├── base_error.rb │ ├── flow.rb │ ├── invalid_response.rb │ ├── node.rb │ └── state.rb └── tasks │ ├── .gitkeep │ ├── consolidation.rake │ ├── jasmine.rake │ └── lint.rake ├── log └── .gitkeep ├── package.json ├── spec ├── components │ ├── all_components_spec.rb │ ├── calendar_spec.rb │ └── download_link_spec.rb ├── factories │ ├── content_items.rb │ └── landing_pages.rb ├── fixtures │ ├── bank-holidays.json │ ├── electoral-result.json │ ├── graphql │ │ ├── best-practice-event.json │ │ ├── best-practice-government-response.json │ │ ├── best-practice-news-story.json │ │ ├── best-practice-press-release.json │ │ ├── news_article.json │ │ ├── news_article_government_response.json │ │ ├── news_article_history_mode.json │ │ ├── news_article_history_mode_translated_arabic.json │ │ ├── news_article_news_story_translated_arabic.json │ │ ├── news_article_press_release.json │ │ ├── news_article_with_image_caption.json │ │ ├── news_article_with_taxons.json │ │ ├── news_article_withdrawn.json │ │ ├── translated_news_article_with_taxon.json │ │ └── world_news_story_news_article.json │ ├── landing_page_statistics_data │ │ ├── data_four.csv │ │ ├── data_one.csv │ │ ├── data_three.csv │ │ └── data_two.csv │ ├── local-content-items │ │ ├── flexible-page.yml │ │ ├── my-json-item.json │ │ └── my-yaml-item.yml │ ├── single-calendar.json │ └── when-do-the-clocks-change.json ├── helpers │ ├── application_helper_spec.rb │ ├── block_helper_spec.rb │ ├── contents_list_helper_spec.rb │ ├── currency_helper_spec.rb │ ├── date_helper_spec.rb │ ├── error_items_helper_spec.rb │ ├── govuk_personalisation_helper_spec.rb │ ├── link_helper_spec.rb │ ├── locale_helper_spec.rb │ ├── parts_navigation_helper_spec.rb │ ├── phone_number_helper_spec.rb │ └── theme_type_helper_spec.rb ├── javascripts │ ├── helpers │ │ ├── .gitkeep │ │ └── nyc-test-coverage-helper.mjs │ ├── reporters │ │ └── nyc-test-coverage-reporter.mjs │ ├── unit │ │ ├── foreign-travel-advice.spec.js │ │ ├── modules │ │ │ ├── main-navigation.spec.js │ │ │ ├── sticky-element-container.spec.js │ │ │ └── transaction-survey-form.spec.js │ │ └── support.spec.js │ └── vendor │ │ └── jquery-1.12.4.js ├── models │ ├── calendar │ │ ├── division_spec.rb │ │ ├── event_spec.rb │ │ └── year_spec.rb │ ├── calendar_spec.rb │ ├── case_study_spec.rb │ ├── content_item_factory_spec.rb │ ├── content_item_spec.rb │ ├── contents_outline_spec.rb │ ├── corporate_information_page_spec.rb │ ├── detailed_guide_spec.rb │ ├── election_postcode_spec.rb │ ├── fatality_notice_spec.rb │ ├── field_of_operation_spec.rb │ ├── fields_of_operation_spec.rb │ ├── finder_spec.rb │ ├── flexible_page │ │ ├── flexible_section │ │ │ ├── page_title_spec.rb │ │ │ └── rich_content_spec.rb │ │ └── flexible_section_factory_spec.rb │ ├── get_involved_spec.rb │ ├── homepage_spec.rb │ ├── landing_page │ │ ├── block │ │ │ ├── action_link_spec.rb │ │ │ ├── base_spec.rb │ │ │ ├── block_error_spec.rb │ │ │ ├── blocks_container_spec.rb │ │ │ ├── box_spec.rb │ │ │ ├── card_spec.rb │ │ │ ├── columns_layout_spec.rb │ │ │ ├── document_list_spec.rb │ │ │ ├── featured_spec.rb │ │ │ ├── govspeak_spec.rb │ │ │ ├── heading_spec.rb │ │ │ ├── hero_spec.rb │ │ │ ├── image_spec.rb │ │ │ ├── layout_base_spec.rb │ │ │ ├── logo_spec.rb │ │ │ ├── main_navigation_spec.rb │ │ │ ├── quote_spec.rb │ │ │ ├── share_links_spec.rb │ │ │ ├── side_navigation_spec.rb │ │ │ └── two_column_layout_spec.rb │ │ └── block_factory_spec.rb │ ├── landing_page_spec.rb │ ├── licence_transaction_spec.rb │ ├── local_authority_spec.rb │ ├── local_transaction_spec.rb │ ├── location_error_spec.rb │ ├── logo_spec.rb │ ├── news_article_spec.rb │ ├── organisation_spec.rb │ ├── place_spec.rb │ ├── service_manual_homepage_spec.rb │ ├── service_manual_service_toolkit_spec.rb │ ├── simple_smart_answer_spec.rb │ ├── specialist_document_spec.rb │ ├── speech_spec.rb │ ├── transaction_spec.rb │ ├── travel_advice_spec.rb │ └── uprn_spec.rb ├── presenter │ ├── address_list_presenter_spec.rb │ ├── content_item_presenter_spec.rb │ ├── contents_outline_presenter_spec.rb │ ├── corporate_information_page_presenter_spec.rb │ ├── electoral_presenter_spec.rb │ ├── faq_presenter_spec.rb │ ├── field_of_operation_presenter_spec.rb │ ├── get_involved_spec.rb │ ├── licence_details_presenter_spec.rb │ ├── local_authority_presenter_spec.rb │ ├── place_presenter_spec.rb │ ├── service_manual_homepage_presenter_spec.rb │ ├── simple_smart_answer_presenter_spec.rb │ ├── specialist_document_presenter_spec.rb │ ├── speech_presenter_spec.rb │ ├── transaction_presenter_spec.rb │ ├── travel_advice_index_presenter_spec.rb │ └── travel_advice_presenter_spec.rb ├── requests │ ├── answer_spec.rb │ ├── calendars_spec.rb │ ├── case_study_spec.rb │ ├── contact_electoral_registration_office_spec.rb │ ├── content_items_controller_spec.rb │ ├── content_loading_problems_spec.rb │ ├── corporate_information_page_spec.rb │ ├── csv_preview_spec.rb │ ├── fatality_notice_spec.rb │ ├── favicon_spec.rb │ ├── field_of_operation_spec.rb │ ├── fields_of_operation_spec.rb │ ├── find_local_council_spec.rb │ ├── flexible_page_spec.rb │ ├── get_involved_spec.rb │ ├── help_spec.rb │ ├── homepage_spec.rb │ ├── landing_page_spec.rb │ ├── licence_transactions_spec.rb │ ├── local_authority_api_spec.rb │ ├── local_transactions_spec.rb │ ├── news_article_spec.rb │ ├── placeholder_spec.rb │ ├── places_spec.rb │ ├── random_spec.rb │ ├── roadmap_spec.rb │ ├── sanitiser_spec.rb │ ├── service_manual_homepage_spec.rb │ ├── service_toolkit_spec.rb │ ├── sessions_spec.rb │ ├── simple_smart_answers_spec.rb │ ├── specialist_document_spec.rb │ ├── speech_spec.rb │ ├── static_error_pages_spec.rb │ ├── take_part_spec.rb │ ├── transactions_spec.rb │ └── travel_advice_spec.rb ├── routing │ ├── calendars_spec.rb │ ├── csv_previews_spec.rb │ ├── landing_pages_spec.rb │ └── simple_smart_answers_spec.rb ├── service_consumers │ └── pact_helper.rb ├── services │ ├── csv_preview_service_spec.rb │ └── electoral_service_spec.rb ├── spec_helper.rb ├── support │ ├── asset_manager_helpers.rb │ ├── calendar_helpers.rb │ ├── component_helpers.rb │ ├── concerns │ │ ├── emphasised_organisations.rb │ │ ├── news_image.rb │ │ ├── parts.rb │ │ ├── people.rb │ │ ├── political.rb │ │ ├── single_page_notification_button.rb │ │ ├── updatable.rb │ │ ├── withdrawable.rb │ │ └── worldwide_organisations.rb │ ├── content_item_helpers.rb │ ├── content_store_helpers.rb │ ├── election_helpers.rb │ ├── jasmine-browser.json │ ├── landing_page_blocks.rb │ ├── location_helpers.rb │ ├── matchers │ │ ├── have_bank_holiday_table_matcher.rb │ │ ├── have_button_as_link_matcher.rb │ │ └── honours_content_store_ttl_matcher.rb │ ├── meta_tags.rb │ ├── publishing_api_graphql_helpers.rb │ ├── schema_org_helpers.rb │ └── search_helpers.rb ├── system │ ├── account_home_spec.rb │ ├── answer_spec.rb │ ├── bank_holidays_spec.rb │ ├── case_study_spec.rb │ ├── corporate_information_page_spec.rb │ ├── csv_preview_spec.rb │ ├── developer_root_spec.rb │ ├── electoral_look_up_spec.rb │ ├── error_handling_spec.rb │ ├── fatality_notice_spec.rb │ ├── field_of_operation_spec.rb │ ├── fields_of_operation_spec.rb │ ├── find_local_council_spec.rb │ ├── flexible_page_spec.rb │ ├── get_involved_spec.rb │ ├── gwyliau_banc_spec.rb │ ├── help_cookies_spec.rb │ ├── help_page_spec.rb │ ├── help_spec.rb │ ├── homepage_spec.rb │ ├── icalendar_spec.rb │ ├── json_spec.rb │ ├── landing_page_spec.rb │ ├── licence_transaction_spec.rb │ ├── local_transactions_spec.rb │ ├── news_article_spec.rb │ ├── phase_banner_spec.rb │ ├── place_spec.rb │ ├── service_manual_homepage_spec.rb │ ├── service_toolkit_spec.rb │ ├── sessions_spec.rb │ ├── sign_in_spec.rb │ ├── simple_smart_answers_spec.rb │ ├── specialist_document_spec.rb │ ├── speech_spec.rb │ ├── static_error_page_spec.rb │ ├── take_part_spec.rb │ ├── transaction_spec.rb │ ├── travel_advice_atom_spec.rb │ ├── travel_advice_spec.rb │ └── when_do_the_clocks_change_spec.rb ├── unit │ ├── api_error_routing_constraint_spec.rb │ ├── content_item_loader_spec.rb │ ├── format_routing_constraint_spec.rb │ ├── full_path_format_routing_constraint_spec.rb │ ├── ics_renderer_spec.rb │ ├── locales_validation_spec.rb │ ├── postcode_sanitizer_spec.rb │ ├── sanitiser │ │ └── strategy_spec.rb │ └── simple_smart_answers │ │ ├── flow_spec.rb │ │ └── node_spec.rb └── views │ └── shared │ └── _published_dates_with_notification_button.html.erb_spec.rb ├── startup.sh └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .git 3 | .gitignore 4 | .github 5 | Dockerfile 6 | Procfile 7 | README.md 8 | coverage 9 | docs 10 | log 11 | node_modules 12 | spec 13 | test 14 | tmp 15 | vendor 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: daily 12 | 13 | - package-ecosystem: docker 14 | directory: / 15 | schedule: 16 | interval: weekly 17 | ignore: 18 | - dependency-name: ruby 19 | 20 | - package-ecosystem: github-actions 21 | directory: / 22 | schedule: 23 | interval: daily 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | ⚠️ This repo is Continuously Deployed: make sure you [follow the guidance](https://docs.publishing.service.gov.uk/manual/development-pipeline.html#merge-your-own-pull-request) ⚠️ 3 | 4 | ## What 5 | 6 | 7 | 8 | ## Why 9 | 10 | [Trello card?](url) 11 | 12 | ## How 13 | 14 | ## Screenshots? 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint GitHub Actions 2 | on: 3 | push: 4 | paths: ['.github/**'] 5 | jobs: 6 | actionlint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | with: 11 | show-progress: false 12 | - uses: alphagov/govuk-infrastructure/.github/actions/actionlint@main 13 | -------------------------------------------------------------------------------- /.github/workflows/jasmine.yml: -------------------------------------------------------------------------------- 1 | name: Run Jasmine 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | run-jasmine: 8 | name: Run Jasmine 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup Ruby 15 | uses: ruby/setup-ruby@v1 16 | with: 17 | bundler-cache: true 18 | 19 | - name: Setup Node 20 | uses: alphagov/govuk-infrastructure/.github/actions/setup-node@main 21 | 22 | - name: Run Jasmine 23 | run: yarn run jasmine:ci 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_run: 6 | workflows: [CI] 7 | types: [completed] 8 | branches: [main] 9 | 10 | jobs: 11 | release: 12 | if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' 13 | name: Release 14 | uses: alphagov/govuk-infrastructure/.github/workflows/release.yml@main 15 | secrets: 16 | GH_TOKEN: ${{ secrets.GOVUK_CI_GITHUB_API_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | .bundle 8 | log/*.log 9 | tmp/ 10 | coverage 11 | public/assets 12 | /app/assets/builds/* 13 | !/app/assets/builds/.keep 14 | node_modules 15 | yarn-error.log 16 | spec/reports/pacts 17 | .env 18 | .yarn/cache/ 19 | .yarn/install-state.gz 20 | 21 | # vim swap files and tags 22 | *.sw[a-z] 23 | /tags 24 | -------------------------------------------------------------------------------- /.govuk_dependabot_merger.yml: -------------------------------------------------------------------------------- 1 | api_version: 2 2 | defaults: 3 | auto_merge: true 4 | update_external_dependencies: true 5 | overrides: 6 | - dependency: rails 7 | auto_merge: false 8 | - dependency: railties 9 | auto_merge: false 10 | - dependency: actioncable 11 | auto_merge: false 12 | - dependency: actionmailbox 13 | auto_merge: false 14 | - dependency: actionmailer 15 | auto_merge: false 16 | - dependency: actionpack 17 | auto_merge: false 18 | - dependency: actiontext 19 | auto_merge: false 20 | - dependency: actionview 21 | auto_merge: false 22 | - dependency: activejob 23 | auto_merge: false 24 | - dependency: activemodel 25 | auto_merge: false 26 | - dependency: activerecord 27 | auto_merge: false 28 | - dependency: activestorage 29 | auto_merge: false 30 | - dependency: activesupport 31 | auto_merge: false 32 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.20.4 2 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "branches": 90, 4 | "lines": 95, 5 | "check-coverage": true, 6 | "compact": false, 7 | "exclude": [ 8 | "public/assets/frontend/component_guide", 9 | "public/assets/frontend/govuk_publishing_components*", 10 | "public/assets/frontend/manifest-*", 11 | "public/assets/frontend/application-*", 12 | "public/assets/frontend/static-error-pages-*", 13 | "public/assets/frontend/test-dependencies-*", 14 | "public/assets/frontend/dependencies-*" 15 | ], 16 | "report-dir": "coverage/javascript", 17 | "reporter": [ 18 | "text" 19 | ], 20 | "source-map": false, 21 | "temp-dir": "tmp/nyc_output" 22 | } 23 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-govuk: 3 | - config/default.yml 4 | - config/rails.yml 5 | - config/rspec.yml 6 | 7 | inherit_mode: 8 | merge: 9 | - Exclude 10 | 11 | # ************************************************************** 12 | # TRY NOT TO ADD OVERRIDES IN THIS FILE 13 | # 14 | # This repo is configured to follow the RuboCop GOV.UK styleguide. 15 | # Any rules you override here will cause this repo to diverge from 16 | # the way we write code in all other GOV.UK repos. 17 | # 18 | # See https://github.com/alphagov/rubocop-govuk/blob/main/CONTRIBUTING.md 19 | # ************************************************************** 20 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.3.6 2 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-3.5.0.cjs 2 | nodeLinker: node-modules 3 | packageExtensions: 4 | stylelint-config-recommended-scss@*: 5 | peerDependencies: 6 | postcss: '8' 7 | stylelint-config-standard-scss@*: 8 | peerDependencies: 9 | postcss: '8' 10 | stylelint-config-gds@*: 11 | peerDependencies: 12 | postcss: '8' 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ruby_version=3.3 2 | ARG base_image=ghcr.io/alphagov/govuk-ruby-base:$ruby_version 3 | ARG builder_image=ghcr.io/alphagov/govuk-ruby-builder:$ruby_version 4 | 5 | 6 | FROM --platform=$TARGETPLATFORM $builder_image AS builder 7 | 8 | WORKDIR $APP_HOME 9 | COPY Gemfile* .ruby-version ./ 10 | RUN bundle install 11 | COPY package.json yarn.lock ./ 12 | RUN npm install -g yarn@1.22.19 && yarn install --immutable 13 | COPY . . 14 | RUN bootsnap precompile --gemfile . 15 | RUN rails assets:precompile && rm -fr log 16 | 17 | 18 | FROM --platform=$TARGETPLATFORM $base_image 19 | 20 | ENV GOVUK_APP_NAME=frontend 21 | 22 | WORKDIR $APP_HOME 23 | COPY --from=builder $BUNDLE_PATH $BUNDLE_PATH 24 | COPY --from=builder $BOOTSNAP_CACHE_DIR $BOOTSNAP_CACHE_DIR 25 | COPY --from=builder $APP_HOME . 26 | 27 | USER app 28 | CMD ["puma"] 29 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p 3005 2 | css: bin/rails dartsass:watch 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | begin 5 | require "pact/tasks" 6 | require "rspec/core/rake_task" 7 | RSpec::Core::RakeTask.new 8 | rescue LoadError 9 | # Pact/RSpec not available in all environments 10 | end 11 | 12 | require File.expand_path("config/application", __dir__) 13 | 14 | Rails.application.load_tasks 15 | 16 | Rake::Task[:default].clear if Rake::Task.task_defined?(:default) 17 | task default: %i[lint spec jasmine pact:verify] 18 | -------------------------------------------------------------------------------- /app/assets/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/builds/.keep -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link application.js 3 | //= link main.js 4 | //= link dependencies.js 5 | //= link test-dependencies.js 6 | //= link views/travel-advice.js 7 | 8 | //= link static-error-pages.js 9 | 10 | //= link_tree ../builds 11 | -------------------------------------------------------------------------------- /app/assets/images/campaign/britain_is_great/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/britain_is_great/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/britain_is_great/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/britain_is_great/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/britain_is_great/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/britain_is_great/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/custom-logos/environment-agency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/custom-logos/environment-agency.png -------------------------------------------------------------------------------- /app/assets/images/campaign/dvla-new-licence-rules/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dvla-new-licence-rules/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/dvla-new-licence-rules/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dvla-new-licence-rules/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/dvla-new-licence-rules/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dvla-new-licence-rules/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/dvla-new-licence-rules/INF45x3W.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dvla-new-licence-rules/INF45x3W.pdf -------------------------------------------------------------------------------- /app/assets/images/campaign/dwp-workplace-pensions/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dwp-workplace-pensions/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/dwp-workplace-pensions/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dwp-workplace-pensions/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/dwp-workplace-pensions/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/dwp-workplace-pensions/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/fire-kills/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/fire-kills/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/fire-kills/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/fire-kills/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/fire-kills/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/fire-kills/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/know-before-you-go/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/know-before-you-go/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/know-before-you-go/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/know-before-you-go/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/know-before-you-go/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/know-before-you-go/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/royal_mail_shares/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/royal_mail_shares/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/royal_mail_shares/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/royal_mail_shares/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/royal_mail_shares/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/royal_mail_shares/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/sort-my-tax/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/sort-my-tax/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/sort-my-tax/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/sort-my-tax/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/sort-my-tax/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/sort-my-tax/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/uk-welcomes/eugo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/uk-welcomes/eugo-logo.png -------------------------------------------------------------------------------- /app/assets/images/campaign/unimoney/320px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/unimoney/320px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/unimoney/768px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/unimoney/768px.jpg -------------------------------------------------------------------------------- /app/assets/images/campaign/unimoney/960px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/campaign/unimoney/960px.jpg -------------------------------------------------------------------------------- /app/assets/images/components/download-link/calendar-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/assets/images/components/download-link/download-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/getsatisfaction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/getsatisfaction.jpg -------------------------------------------------------------------------------- /app/assets/images/homepage/cost-of-living-featured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/homepage/cost-of-living-featured.png -------------------------------------------------------------------------------- /app/assets/images/homepage/find-a-job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/homepage/find-a-job.png -------------------------------------------------------------------------------- /app/assets/images/homepage/national-insurance-featured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/homepage/national-insurance-featured.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/960x640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/960x640.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/chart.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/desktop.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/desktop_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/desktop_2x.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/landing_page_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/landing_page_image.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_featured_desktop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_featured_desktop.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_featured_desktop_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_featured_desktop_2x.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_featured_mobile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_featured_mobile.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_featured_mobile_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_featured_mobile_2x.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_featured_tablet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_featured_tablet.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_featured_tablet_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_featured_tablet_2x.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_hero_desktop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_hero_desktop.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_hero_desktop_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_hero_desktop_2x.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_hero_mobile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_hero_mobile.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_hero_mobile_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_hero_mobile_2x.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_hero_tablet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_hero_tablet.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/missions_hero_tablet_2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/missions_hero_tablet_2x.jpg -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/mobile.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/mobile_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/mobile_2x.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/tablet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/tablet.png -------------------------------------------------------------------------------- /app/assets/images/landing_page/placeholder/tablet_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/landing_page/placeholder/tablet_2x.png -------------------------------------------------------------------------------- /app/assets/images/ministry-of-defence-crest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/ministry-of-defence-crest.png -------------------------------------------------------------------------------- /app/assets/images/roadmap/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/roadmap/image.png -------------------------------------------------------------------------------- /app/assets/images/roadmap/updates-blog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/roadmap/updates-blog.jpg -------------------------------------------------------------------------------- /app/assets/images/roadmap/updates-goals.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/roadmap/updates-goals.jpg -------------------------------------------------------------------------------- /app/assets/images/roadmap/updates-work.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/roadmap/updates-work.jpg -------------------------------------------------------------------------------- /app/assets/images/specialist-documents/protected-food-drink-names/protected-designation-of-origin-pdo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/specialist-documents/protected-food-drink-names/protected-designation-of-origin-pdo.png -------------------------------------------------------------------------------- /app/assets/images/specialist-documents/protected-food-drink-names/protected-geographical-indication-pgi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/specialist-documents/protected-food-drink-names/protected-geographical-indication-pgi.png -------------------------------------------------------------------------------- /app/assets/images/specialist-documents/protected-food-drink-names/traditional-speciality-guaranteed-tsg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/specialist-documents/protected-food-drink-names/traditional-speciality-guaranteed-tsg.png -------------------------------------------------------------------------------- /app/assets/images/start-pages/make-a-sorn/performance-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/start-pages/make-a-sorn/performance-icon-2x.png -------------------------------------------------------------------------------- /app/assets/images/start-pages/make-a-sorn/performance-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/start-pages/make-a-sorn/performance-icon.png -------------------------------------------------------------------------------- /app/assets/images/templogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/templogo.png -------------------------------------------------------------------------------- /app/assets/images/travel-advice/feed-icon-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/travel-advice/feed-icon-black.png -------------------------------------------------------------------------------- /app/assets/images/travel-advice/mail-icon-x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/travel-advice/mail-icon-x2.png -------------------------------------------------------------------------------- /app/assets/images/travel-advice/mail-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/app/assets/images/travel-advice/mail-icon.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This file is linked to in the application. 2 | //= require dependencies 3 | //= require main 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/dependencies.js: -------------------------------------------------------------------------------- 1 | // This file contains the dependencies required by the application. 2 | //= require govuk_publishing_components/lib 3 | //= require govuk_publishing_components/components/contents-list-with-body 4 | //= require govuk_publishing_components/components/error-summary 5 | //= require govuk_publishing_components/components/govspeak 6 | //= require govuk_publishing_components/components/image-card 7 | //= require govuk_publishing_components/components/intervention 8 | //= require govuk_publishing_components/components/metadata 9 | //= require govuk_publishing_components/components/print-link 10 | //= require govuk_publishing_components/components/radio 11 | //= require govuk_publishing_components/components/step-by-step-nav 12 | //= require govuk_publishing_components/components/table 13 | //= require govuk_publishing_components/components/tabs 14 | 15 | //= require govuk_web_banners/dependencies 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/main.js: -------------------------------------------------------------------------------- 1 | // This file should only contain the code being a part 2 | // of this application, not its dependencies. 3 | // It will be checked with respect to test coverage. 4 | //= require support 5 | //= require_tree ./modules 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/static-error-pages.js: -------------------------------------------------------------------------------- 1 | //= require govuk_publishing_components/dependencies 2 | 3 | //= require govuk_publishing_components/lib/trigger-event 4 | //= require govuk_publishing_components/lib/cookie-functions 5 | //= require govuk_publishing_components/lib/extend-object 6 | 7 | //= require govuk_publishing_components/components/button 8 | //= require govuk_publishing_components/components/cookie-banner 9 | //= require govuk_publishing_components/components/cross-service-header 10 | //= require govuk_publishing_components/components/feedback 11 | //= require govuk_publishing_components/components/layout-header 12 | //= require govuk_publishing_components/components/layout-super-navigation-header 13 | //= require govuk_publishing_components/components/skip-link 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/support.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | if (typeof window.GOVUK === 'undefined') { window.GOVUK = {} } 3 | /* istanbul ignore next */ 4 | if (typeof window.GOVUK.support === 'undefined') { window.GOVUK.support = {} } 5 | 6 | window.GOVUK.support.history = function () { 7 | return window.history && window.history.pushState && window.history.replaceState 8 | } 9 | -------------------------------------------------------------------------------- /app/assets/javascripts/test-dependencies.js: -------------------------------------------------------------------------------- 1 | // This file contains dependencies that are only needed when running in a test 2 | // environment. In the dev and live environment these are provided by static. 3 | //= require govuk_publishing_components/dependencies 4 | //= require govuk_publishing_components/lib/cookie-functions 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | // This flag stops the font from being included in this application's 2 | // stylesheet - the font is being served by Static across all of GOV.UK, so is 3 | // not needed here. 4 | $govuk-include-default-font-face: false; 5 | 6 | @import "govuk_publishing_components/govuk_frontend_support"; 7 | 8 | // Helper stylesheets (things on more than one page layout) 9 | @import "helpers/content-bottom-margin"; 10 | @import "helpers/inverse-background"; 11 | @import "helpers/sticky-element-container"; 12 | 13 | // frontend mixins 14 | @import "mixins/margins"; 15 | 16 | .article-container { 17 | margin-bottom: govuk-spacing(6); 18 | 19 | @include govuk-media-query($from: tablet) { 20 | @include responsive-bottom-margin; 21 | } 22 | } 23 | 24 | .direction-rtl .govuk-main-wrapper { 25 | direction: rtl; 26 | text-align: start; 27 | 28 | table th { 29 | text-align: start; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/_calendar.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .app-c-calendar { 4 | .govuk-table { 5 | @include govuk-font($size: 16); 6 | } 7 | 8 | td:nth-child(1), 9 | td:nth-child(2) { 10 | width: 25%; 11 | } 12 | 13 | td:nth-child(3) { 14 | width: 50%; 15 | } 16 | } 17 | 18 | .app-c-calendar--clocks { 19 | margin-bottom: govuk-spacing(8); 20 | 21 | td:nth-child(1) { 22 | width: 10%; 23 | } 24 | 25 | td:nth-child(2), 26 | td:nth-child(3) { 27 | width: 45%; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/_download-link.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .app-c-download-link { 4 | display: inline-block; 5 | padding-left: 25px; 6 | background-image: url("components/download-link/download-icon.svg"); 7 | background-repeat: no-repeat; 8 | background-position: 0 3px; 9 | background-size: 20px 20px; 10 | } 11 | 12 | .app-c-download-link--calendar { 13 | padding-left: 26px; 14 | background-image: url("components/download-link/calendar-icon.svg"); 15 | background-position: 0 2px; 16 | background-size: 18px 21px; 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/helpers/_content-bottom-margin.scss: -------------------------------------------------------------------------------- 1 | .content-bottom-margin { 2 | margin-bottom: govuk-spacing(4); 3 | 4 | @include govuk-media-query($from: tablet) { 5 | @include responsive-bottom-margin; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/helpers/_inverse-background.scss: -------------------------------------------------------------------------------- 1 | .inverse-background { 2 | background: $govuk-brand-colour; 3 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/helpers/_parts.scss: -------------------------------------------------------------------------------- 1 | @mixin parts { 2 | .part-navigation-container { 3 | margin-top: govuk-spacing(6); 4 | margin-bottom: govuk-spacing(6); 5 | padding-bottom: govuk-spacing(3); 6 | border-bottom: 1px solid govuk-colour("mid-grey"); 7 | 8 | @include govuk-media-query($from: tablet) { 9 | margin-top: 0; 10 | margin-bottom: 0; 11 | } 12 | } 13 | 14 | .part-navigation { 15 | margin-left: 0; 16 | 17 | @include govuk-media-query($until: tablet) { 18 | margin-left: govuk-spacing(1); 19 | } 20 | } 21 | 22 | .part-title { 23 | margin-bottom: govuk-spacing(3); 24 | margin-top: govuk-spacing(3); 25 | 26 | @include govuk-font(27, $weight: bold); 27 | 28 | @include govuk-media-query($from: tablet) { 29 | margin-bottom: govuk-spacing(4); 30 | margin-top: govuk-spacing(4); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/helpers/_sticky-element-container.scss: -------------------------------------------------------------------------------- 1 | .govuk-frontend-supported .sticky-element { 2 | position: absolute; 3 | bottom: 0; 4 | 5 | &--stuck-to-window { 6 | bottom: 0; 7 | position: fixed; 8 | } 9 | 10 | &--enabled { 11 | transition: opacity, .3s, ease; 12 | opacity: 1; 13 | 14 | @include govuk-media-query($until: tablet) { 15 | position: static; 16 | } 17 | } 18 | 19 | &--hidden { 20 | opacity: 0; 21 | pointer-events: none; 22 | } 23 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/helpers/_truncated-url.scss: -------------------------------------------------------------------------------- 1 | .truncated-url { 2 | display: block; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | white-space: nowrap; 6 | max-width: 36ch; 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static-error-pages.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "govuk_publishing_components/govuk_frontend_support"; 3 | @import "govuk_publishing_components/component_support"; 4 | 5 | @import "govuk_publishing_components/components/button"; 6 | @import "govuk_publishing_components/components/cookie-banner"; 7 | @import "govuk_publishing_components/components/cross-service-header"; 8 | @import "govuk_publishing_components/components/feedback"; 9 | @import "govuk_publishing_components/components/label"; 10 | @import "govuk_publishing_components/components/layout-footer"; 11 | @import "govuk_publishing_components/components/layout-for-public"; 12 | @import "govuk_publishing_components/components/layout-header"; 13 | @import "govuk_publishing_components/components/layout-super-navigation-header"; -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_cookie-settings.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .cookie-settings__form-wrapper { 4 | display: none; 5 | 6 | .govuk-frontend-supported & { 7 | display: block; 8 | } 9 | } 10 | 11 | .cookie-settings__no-js { 12 | .govuk-frontend-supported & { 13 | display: none; 14 | } 15 | } 16 | 17 | .cookie-settings__confirmation { 18 | display: none; 19 | margin-top: govuk-spacing(3); 20 | @include govuk-font(19); 21 | } 22 | 23 | .cookie-settings__gov-services { 24 | margin-top: govuk-spacing(8); 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_homepage_more_on_govuk.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .homepage-most-active-list { 4 | list-style: none; 5 | margin: 0 0 govuk-spacing(9); 6 | padding: 0; 7 | } 8 | 9 | .homepage-most-active-list__item { 10 | margin: 0 0 govuk-spacing(4); 11 | @include govuk-font($size: 19, $weight: bold); 12 | 13 | &:last-of-type { 14 | margin-bottom: 0; 15 | } 16 | 17 | // Ensure font-size is 19px on mobile for the new homepage design 18 | @include govuk-media-query($until: "tablet") { 19 | font-size: 19px; 20 | font-size: govuk-px-to-rem(19); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/block-error.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/govuk_frontend_support"; 2 | 3 | .block-error { 4 | padding: govuk-spacing(3); 5 | border: $govuk-border-width-narrow solid $govuk-error-colour; 6 | @include govuk-responsive-margin(8, "bottom"); 7 | 8 | @include govuk-media-query($from: tablet) { 9 | padding: govuk-spacing(4); 10 | border-width: $govuk-border-width; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/box.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .box { 4 | padding: govuk-spacing(4); 5 | background-color: govuk-colour("light-grey"); 6 | } 7 | 8 | @include govuk-media-query($media-type: print) { 9 | .box { 10 | background: none; 11 | border-top: 2pt solid $govuk-print-text-colour; 12 | padding-inline: 0; 13 | 14 | * { 15 | color: $govuk-print-text-colour !important; // stylelint-disable-line declaration-no-important 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/columns_layout.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .columns-layout { 4 | display: grid; 5 | grid-gap: govuk-spacing(6); 6 | 7 | @include govuk-media-query($from: desktop) { 8 | @for $i from 2 through 3 { 9 | &[data-columns="#{$i}"]{ 10 | grid-template-columns: repeat(#{$i}, 1fr); 11 | } 12 | } 13 | } 14 | 15 | } 16 | @include govuk-media-query($media-type: print) { 17 | .columns-layout { 18 | display: block; 19 | 20 | > * { 21 | margin-bottom: govuk-spacing(6); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/helpers/_backgrounds.scss: -------------------------------------------------------------------------------- 1 | @import "colours"; 2 | 3 | @each $name, $colour in $colours { 4 | .background-color--theme-#{$name} { 5 | background-color: $colour; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/helpers/_borders.scss: -------------------------------------------------------------------------------- 1 | @import "colours"; 2 | 3 | @each $name, $colour in $colours { 4 | .border-top--theme-#{$name} { 5 | border-top: 20px solid $colour; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/helpers/_colours.scss: -------------------------------------------------------------------------------- 1 | $theme-1-colour: #D60061; 2 | $theme-2-colour: #92BA15; 3 | $theme-3-colour: #E68621; 4 | $theme-4-colour: #B000F9; 5 | $theme-5-colour: #255AB5; 6 | $theme-6-colour: #62162D; 7 | 8 | // The following map is used by the borders and backgrounds helpers to iterate through 'theme' color options and 9 | // generate utility classes for each color variation e.g. 10 | // .border-top--theme-1 { 11 | // border-top: 20px solid #e60067; 12 | // } 13 | 14 | $colours: ( 15 | "default": govuk-colour("black"), // This helps us see where branding was missed, as we don't want to fall back to an actual brand colour. 16 | "1": $theme-1-colour, 17 | "2": $theme-2-colour, 18 | "3": $theme-3-colour, 19 | "4": $theme-4-colour, 20 | "5": $theme-5-colour, 21 | "6": $theme-6-colour, 22 | ); 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/image.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .image { 4 | width: 100%; 5 | @include govuk-responsive-margin(6, "bottom"); 6 | } 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/logo.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .logo { 4 | margin-bottom: govuk-spacing(6); 5 | } 6 | 7 | .logo__img { 8 | display: block; 9 | } 10 | 11 | .logo__img--nhs { 12 | max-width: 100px; 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/quote.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .quote__svg { 4 | margin-bottom: govuk-spacing(4); 5 | } 6 | 7 | .quote__blockquote { 8 | margin: 0; 9 | } 10 | 11 | .quote__text { 12 | margin: 0 0 govuk-spacing(8); 13 | @include govuk-font(19, $weight: "regular", $tabular: false, $line-height: false); 14 | } 15 | 16 | .quote__cite { 17 | font-style: normal; 18 | @include govuk-font(19, $weight: "regular", $tabular: false, $line-height: false); 19 | } 20 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/side-navigation.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .side-nav { 4 | @include govuk-media-query($until: desktop) { 5 | display: none; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_landing_page/themes/prime-ministers-office-10-downing-street.scss: -------------------------------------------------------------------------------- 1 | .landing-page-header { 2 | // I've used the hex value for the background colour as it doesn't exist in the colour palette 3 | background-color: #231F20; 4 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_local-transaction.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .interaction { 4 | margin-bottom: govuk-spacing(6); 5 | 6 | .local-authority { 7 | font-weight: bold; 8 | } 9 | } 10 | 11 | .search-again { 12 | margin: govuk-spacing(7) 0 govuk-spacing(3); 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_location_form.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .location-form { 4 | padding: govuk-spacing(3); 5 | margin: govuk-spacing(6) 0; 6 | background: govuk-colour("light-grey"); 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_place-list.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | @import "helpers/truncated-url"; 3 | 4 | .place-list__item { 5 | margin-top: govuk-spacing(5); 6 | padding-top: govuk-spacing(2); 7 | border-top: 1px solid $govuk-border-colour; 8 | list-style: none; 9 | 10 | &:first-child { 11 | margin-top: 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_published-dates-button-group.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .published-dates-button-group { 4 | @include govuk-media-query($from: tablet) { 5 | display: flex; 6 | flex-direction: row; 7 | } 8 | } 9 | 10 | .published-dates-button-group form { 11 | @include govuk-media-query($until: tablet) { 12 | margin-bottom: govuk-spacing(3); 13 | } 14 | @include govuk-media-query($from: tablet) { 15 | margin-right: govuk-spacing(4); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_publisher_metadata.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .metadata-wrapper { 4 | border-top: 1px solid $govuk-border-colour; 5 | margin-left: govuk-spacing(3); 6 | margin-right: govuk-spacing(3); 7 | @include govuk-clearfix; 8 | 9 | .metadata-column { 10 | padding-left: 0; 11 | padding-top: govuk-spacing(3); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_roadmap.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .govuk-roadmap__intro { 4 | @include govuk-font(24); 5 | } 6 | 7 | .govuk-roadmap-section { 8 | border-bottom: 5px solid govuk-colour("black"); 9 | margin-bottom: govuk-spacing(6); 10 | padding-bottom: govuk-spacing(6); 11 | } 12 | 13 | .govuk-roadmap-section__image { 14 | max-width: 300px; 15 | margin: 0 auto; 16 | } 17 | 18 | .govuk-roadmap-numbers__column { 19 | @include govuk-media-query($until: desktop) { 20 | width: 50%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_service-toolkit.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .service-toolkit { 4 | border-bottom: 1px solid $govuk-border-colour; 5 | margin-bottom: govuk-spacing(5); 6 | padding-bottom: govuk-spacing(3); 7 | 8 | @include govuk-media-query($from: tablet) { 9 | margin-bottom: govuk-spacing(9); 10 | padding-bottom: govuk-spacing(6); 11 | } 12 | } 13 | 14 | .service-toolkit:last-child { 15 | border-bottom: none; 16 | margin-bottom: 0; 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_sidebar-navigation.scss: -------------------------------------------------------------------------------- 1 | @import "govuk_publishing_components/individual_component_support"; 2 | 3 | .sidebar-navigation { 4 | @include govuk-media-query($until: tablet) { 5 | margin-top: govuk-spacing(7); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/_specialist-document.scss: -------------------------------------------------------------------------------- 1 | .specialist-document { 2 | .protected-food-drink-name-logo { 3 | float: right; 4 | width: 150px; 5 | padding-left: 2em; 6 | padding-bottom: 2em; 7 | } 8 | } -------------------------------------------------------------------------------- /app/controllers/account_home_controller.rb: -------------------------------------------------------------------------------- 1 | class AccountHomeController < ApplicationController 2 | include GovukPersonalisation::ControllerConcern 3 | 4 | def show 5 | redirect_with_analytics GovukPersonalisation::Urls.one_login_your_services 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/answer_controller.rb: -------------------------------------------------------------------------------- 1 | class AnswerController < ContentItemsController 2 | include Cacheable 3 | 4 | def show 5 | @presenter = ContentItemPresenter.new(content_item) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/case_study_controller.rb: -------------------------------------------------------------------------------- 1 | class CaseStudyController < ContentItemsController 2 | def show 3 | @case_study_presenter = ContentItemPresenter.new(content_item) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/concerns/cacheable.rb: -------------------------------------------------------------------------------- 1 | module Cacheable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :set_expiry, if: -> { request.format.html? } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/concerns/previewable.rb: -------------------------------------------------------------------------------- 1 | module Previewable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :set_edition_for_viewing_draft_content 6 | end 7 | 8 | def set_edition_for_viewing_draft_content 9 | @edition = params[:edition] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/corporate_information_page_controller.rb: -------------------------------------------------------------------------------- 1 | class CorporateInformationPageController < ContentItemsController 2 | def show 3 | @presenter = CorporateInformationPagePresenter.new(@content_item) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/development_controller.rb: -------------------------------------------------------------------------------- 1 | class DevelopmentController < ApplicationController 2 | layout false 3 | 4 | def index 5 | @paths = YAML.load_file(Rails.root.join("config/govuk_examples.yml")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/error_controller.rb: -------------------------------------------------------------------------------- 1 | class ErrorController < ApplicationController 2 | def handler 3 | # We know at this point that the ContentItemLoader has stored 4 | # an exception to deal with, so just retrieve it and raise it 5 | # to be handled in ApplicationController 6 | raise ContentItemLoader.for_request(request).load(request.path) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/fatality_notice_controller.rb: -------------------------------------------------------------------------------- 1 | class FatalityNoticeController < ContentItemsController 2 | include Cacheable 3 | 4 | def show 5 | @presenter = ContentItemPresenter.new(content_item) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/favicon_controller.rb: -------------------------------------------------------------------------------- 1 | # Because favicon has to be present at root, we need a controller 2 | # to redirect it to the asset. When there's a better solution to this 3 | # problem we can remove this controller and routes. 4 | class FaviconController < ApplicationController 5 | before_action { expires_in(1.day, public: true) } 6 | 7 | def redirect_to_asset 8 | redirect_to(view_context.asset_path("favicon.ico"), 9 | status: :moved_permanently, 10 | allow_other_host: true) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/field_of_operation_controller.rb: -------------------------------------------------------------------------------- 1 | class FieldOfOperationController < ContentItemsController 2 | include Cacheable 3 | def show 4 | @presenter = FieldOfOperationPresenter.new(@content_item) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/fields_of_operation_controller.rb: -------------------------------------------------------------------------------- 1 | class FieldsOfOperationController < ContentItemsController 2 | include Cacheable 3 | 4 | def index; end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/flexible_page_controller.rb: -------------------------------------------------------------------------------- 1 | class FlexiblePageController < ContentItemsController 2 | slimmer_template "gem_layout_full_width" 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/get_involved_controller.rb: -------------------------------------------------------------------------------- 1 | class GetInvolvedController < ContentItemsController 2 | def show 3 | @presenter = GetInvolvedPresenter.new(@content_item) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/help_controller.rb: -------------------------------------------------------------------------------- 1 | class HelpController < ContentItemsController 2 | include Cacheable 3 | 4 | skip_before_action :set_expiry, only: [:ab_testing] 5 | skip_before_action :set_locale, only: [:ab_testing] 6 | 7 | def index; end 8 | 9 | def cookie_settings; end 10 | 11 | def ab_testing 12 | ab_test = GovukAbTesting::AbTest.new("Example") 13 | @requested_variant = ab_test.requested_variant(request.headers) 14 | @requested_variant.configure_response(response) 15 | end 16 | 17 | def sign_in 18 | @search_services_facets = [ 19 | { 20 | key: "content_purpose_supergroup[]", 21 | value: "services", 22 | }, 23 | { 24 | key: "content_purpose_supergroup[]", 25 | value: "guidance_and_regulation", 26 | }, 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/help_page_controller.rb: -------------------------------------------------------------------------------- 1 | class HelpPageController < ContentItemsController 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/homepage_controller.rb: -------------------------------------------------------------------------------- 1 | class HomepageController < ContentItemsController 2 | include Cacheable 3 | 4 | slimmer_template "gem_layout_homepage" 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/landing_page_controller.rb: -------------------------------------------------------------------------------- 1 | class LandingPageController < ContentItemsController 2 | slimmer_template "gem_layout_full_width" 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/news_article_controller.rb: -------------------------------------------------------------------------------- 1 | class NewsArticleController < ContentItemsController 2 | include Cacheable 3 | 4 | def show 5 | @presenter = ContentItemPresenter.new(content_item) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/placeholder_controller.rb: -------------------------------------------------------------------------------- 1 | class PlaceholderController < ApplicationController 2 | def show; end 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/roadmap_controller.rb: -------------------------------------------------------------------------------- 1 | class RoadmapController < ApplicationController 2 | include Cacheable 3 | 4 | def index; end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/service_manual_controller.rb: -------------------------------------------------------------------------------- 1 | class ServiceManualController < ContentItemsController 2 | include Cacheable 3 | slimmer_template "gem_layout_full_width" 4 | 5 | def index 6 | @presenter = ServiceManualHomepagePresenter.new(content_item) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/service_toolkit_controller.rb: -------------------------------------------------------------------------------- 1 | class ServiceToolkitController < ContentItemsController 2 | include Cacheable 3 | slimmer_template "gem_layout_full_width" 4 | 5 | def index; end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/specialist_document_controller.rb: -------------------------------------------------------------------------------- 1 | class SpecialistDocumentController < ContentItemsController 2 | include Cacheable 3 | 4 | def show 5 | @presenter = SpecialistDocumentPresenter.new(content_item) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/speech_controller.rb: -------------------------------------------------------------------------------- 1 | class SpeechController < ContentItemsController 2 | def show 3 | @presenter = SpeechPresenter.new(content_item) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/static_error_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticErrorPagesController < ApplicationController 2 | after_action do 3 | response.headers[Slimmer::Headers::SKIP_HEADER] = "true" 4 | end 5 | 6 | ERROR_CODES = %w[ 7 | 400 8 | 401 9 | 403 10 | 404 11 | 405 12 | 406 13 | 410 14 | 422 15 | 429 16 | 500 17 | 501 18 | 502 19 | 503 20 | 504 21 | ].freeze 22 | 23 | def show 24 | if ERROR_CODES.include?(params[:error_code]) 25 | render action: params[:error_code], layout: false 26 | else 27 | head :not_found 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/take_part_controller.rb: -------------------------------------------------------------------------------- 1 | class TakePartController < ContentItemsController 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/transaction_controller.rb: -------------------------------------------------------------------------------- 1 | class TransactionController < ContentItemsController 2 | include Cacheable 3 | include LocaleHelper 4 | 5 | before_action :deny_framing 6 | 7 | def show 8 | content_item.set_variant(params["variant"]) 9 | @transaction_presenter = TransactionPresenter.new(content_item) 10 | end 11 | 12 | private 13 | 14 | def content_item_path 15 | "/#{params[:slug]}" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/block_helper.rb: -------------------------------------------------------------------------------- 1 | module BlockHelper 2 | def render_block(block, options: {}) 3 | render("landing_page/blocks/#{block.type}", block:, options:) 4 | rescue ActionView::MissingTemplate 5 | Rails.logger.warn("Missing template for block #{block.type}") 6 | "" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/helpers/calendar_helper.rb: -------------------------------------------------------------------------------- 1 | module CalendarHelper 2 | def last_updated_date 3 | File.mtime(Rails.root.join("REVISION")).to_date 4 | rescue StandardError 5 | Time.zone.today 6 | end 7 | 8 | def time_tag_safe(date) 9 | tag.time(datetime: date) { l(date, format: "%e %B") }.html_safe 10 | end 11 | 12 | def format_date(date) 13 | l date, format: "%e %B %Y" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/contents_list_helper.rb: -------------------------------------------------------------------------------- 1 | module ContentsListHelper 2 | def contents_list(current_path, links) 3 | links.map do |link| 4 | content = { 5 | href: link["href"], 6 | text: link["text"], 7 | } 8 | 9 | content[:active] = true if current_path == link["href"] 10 | content[:links] = contents_list(current_path, link["links"]) if link["links"].present? 11 | 12 | content 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/currency_helper.rb: -------------------------------------------------------------------------------- 1 | module CurrencyHelper 2 | def format_amount(number) 3 | if number.present? 4 | number_to_currency(number, unit: "euros", precision: 0, format: "%n %u") 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/helpers/date_helper.rb: -------------------------------------------------------------------------------- 1 | module DateHelper 2 | def display_date(timestamp, format = "%-d %B %Y") 3 | I18n.l(Time.zone.parse(timestamp), format:, locale: I18n.locale) if timestamp 4 | end 5 | 6 | def formatted_history(history) 7 | history.map do |change| 8 | { 9 | display_time: display_date(change[:timestamp]), 10 | note: change[:note], 11 | timestamp: change[:timestamp], 12 | } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/draft_helper.rb: -------------------------------------------------------------------------------- 1 | module DraftHelper 2 | def draft_host? 3 | ENV["PLEK_HOSTNAME_PREFIX"] == "draft-" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/error_items_helper.rb: -------------------------------------------------------------------------------- 1 | module ErrorItemsHelper 2 | def error_items(field) 3 | if flash[:validation] && flash[:validation].select { |key| key.to_s.match(field) }.any? 4 | sanitize(flash[:validation] 5 | .select { |key| key.to_s.match(field) } 6 | .map { |error| error[:text] } 7 | .join("
")) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/helpers/govuk_personalisation_helper.rb: -------------------------------------------------------------------------------- 1 | module GovukPersonalisationHelper 2 | def email_subscription_success_banner_heading(account_flash, locale = nil) 3 | if account_flash.include?("email-subscription-success") 4 | sanitize(t("email.subscribe_title", locale:)) 5 | elsif account_flash.include?("email-unsubscribe-success") 6 | sanitize(t("email.unsubscribe_title", locale:)) 7 | elsif account_flash.include?("email-subscription-already-subscribed") 8 | sanitize(t("email.already_subscribed_title", locale:)) 9 | end 10 | end 11 | 12 | def show_email_subscription_success_banner?(account_flash) 13 | account_flash.include?("email-subscription-success") || account_flash.include?("email-unsubscribe-success") || account_flash.include?("email-subscription-already-subscribed") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/location_form_helper.rb: -------------------------------------------------------------------------------- 1 | module LocationFormHelper 2 | def button_text(publication_format = nil, publication_title = nil) 3 | case publication_format 4 | when "local_transaction", "licence" 5 | I18n.t("formats.local_transaction.find_council") 6 | when "place" 7 | places_button_text(publication_title) 8 | else 9 | I18n.t("find") 10 | end 11 | end 12 | 13 | def places_button_text(publication_title) 14 | publications_where_button_text_matches_title = ["Find a register office", "Darganfod swyddfa gofrestru"] 15 | publications_where_button_text_matches_title.include?(publication_title) ? publication_title : I18n.t("formats.place.find_results") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/phone_number_helper.rb: -------------------------------------------------------------------------------- 1 | module PhoneNumberHelper 2 | UK_PHONE_REGEX = /^((\(?0\d{2}\)?\s?\d{4}\s?\d{4}))?/ 3 | 4 | def phone_digits(phone_number) 5 | UK_PHONE_REGEX.match(phone_number)[0] 6 | end 7 | 8 | def phone_text(phone_number) 9 | UK_PHONE_REGEX.match(phone_number).post_match.strip 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/helpers/theme_type_helper.rb: -------------------------------------------------------------------------------- 1 | module ThemeTypeHelper 2 | def style(theme_colour) 3 | valid_numbers = (1..6) 4 | return "theme-default" unless valid_numbers.include?(theme_colour) 5 | 6 | "theme-#{theme_colour}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/helpers/travel_advice_helper.rb: -------------------------------------------------------------------------------- 1 | require "htmlentities" 2 | 3 | module TravelAdviceHelper 4 | def format_atom_change_description(text) 5 | # Encode basic entities([<>&'"]) as named, the rest as decimal 6 | simple_format(HTMLEntities.new.encode(text, :basic, :decimal)) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/helpers/url_helper.rb: -------------------------------------------------------------------------------- 1 | module UrlHelper 2 | def canonical_url(path) 3 | Plek.new.website_root + path 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/calendar/event.rb: -------------------------------------------------------------------------------- 1 | require "ostruct" 2 | 3 | class Calendar 4 | class Event < OpenStruct 5 | def initialize(attributes) 6 | attributes["date"] = Date.parse(attributes["date"]) unless attributes["date"].is_a?(Date) 7 | if attributes["title"] && attributes["title"].present? 8 | attributes["title"] = I18n.t(attributes["title"]) 9 | end 10 | if attributes["notes"] && attributes["notes"].present? 11 | attributes["notes"] = I18n.t(attributes["notes"]) 12 | end 13 | super(attributes) 14 | end 15 | 16 | def as_json 17 | @table.stringify_keys 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/calendar/year.rb: -------------------------------------------------------------------------------- 1 | class Calendar 2 | class Year 3 | def initialize(year_str, division, data = []) 4 | @year_str = year_str 5 | @division = division 6 | @data = data 7 | end 8 | 9 | def to_s 10 | @year_str 11 | end 12 | 13 | def events 14 | @events ||= @data.map do |e| 15 | Event.new(e) 16 | end 17 | end 18 | 19 | def upcoming_event 20 | @upcoming_event ||= events.find { |e| e.date >= Time.zone.today } 21 | end 22 | 23 | def upcoming_events 24 | @upcoming_events ||= events.select { |e| e.date >= Time.zone.today } 25 | end 26 | 27 | def past_events 28 | @past_events ||= events.select { |e| e.date < Time.zone.today } 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/models/case_study.rb: -------------------------------------------------------------------------------- 1 | class CaseStudy < ContentItem 2 | include EmphasisedOrganisations 3 | include Updatable 4 | include WorldwideOrganisations 5 | 6 | def contributors 7 | (organisations_ordered_by_emphasis + worldwide_organisations).uniq(&:content_id) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/concerns/emphasised_organisations.rb: -------------------------------------------------------------------------------- 1 | module EmphasisedOrganisations 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | def organisations_ordered_by_emphasis 6 | organisations.sort_by { |organisation| emphasised?(organisation) ? -1 : 1 } 7 | end 8 | end 9 | 10 | private 11 | 12 | def emphasised?(organisation) 13 | organisation.content_id.in?(emphasised_organisations) 14 | end 15 | 16 | def emphasised_organisations 17 | content_store_response.dig("details", "emphasised_organisations") || [] 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/concerns/news_image.rb: -------------------------------------------------------------------------------- 1 | module NewsImage 2 | extend ActiveSupport::Concern 3 | 4 | def image 5 | content_store_response.dig("details", "image") || default_news_image || placeholder_image 6 | end 7 | 8 | private 9 | 10 | def default_news_image 11 | organisation = content_store_response.dig("links", "primary_publishing_organisation") 12 | organisation[0].dig("details", "default_news_image") if organisation.present? 13 | end 14 | 15 | def placeholder_image 16 | # this image has been uploaded to asset-manager 17 | return { "url" => "https://assets.publishing.service.gov.uk/media/5e985599d3bf7f3fc943bbd8/UK_government_logo.jpg" } if content_store_response["document_type"] == "world_news_story" 18 | 19 | { "url" => "https://assets.publishing.service.gov.uk/media/5e59279b86650c53b2cefbfe/placeholder.jpg" } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/concerns/people.rb: -------------------------------------------------------------------------------- 1 | module People 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | def people 6 | linked("people") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/concerns/political.rb: -------------------------------------------------------------------------------- 1 | module Political 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | def historically_political? 6 | political? && historical? 7 | end 8 | 9 | def publishing_government 10 | content_store_response.dig( 11 | "links", "government", 0, "title" 12 | ) 13 | end 14 | end 15 | 16 | private 17 | 18 | def political? 19 | content_store_response.dig("details", "political") 20 | end 21 | 22 | def historical? 23 | government_current = content_store_response.dig( 24 | "links", "government", 0, "details", "current" 25 | ) 26 | 27 | # Treat no government as not historical 28 | return false if government_current.nil? 29 | 30 | !government_current 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/models/concerns/single_page_notification_button.rb: -------------------------------------------------------------------------------- 1 | module SinglePageNotificationButton 2 | extend ActiveSupport::Concern 3 | 4 | # Add the content id of the publication, detailed_guide or consultation that should be exempt from having the single page notification button 5 | EXEMPTION_LIST = %w[ 6 | c5c8d3cd-0dc2-4ca3-8672-8ca0a6e92165 7 | 70bd3a76-6606-45dd-9fb5-2b95f8667b4d 8 | a457220c-915c-4cb1-8e41-9191fba42540 9 | 5f9c6c15-7631-11e4-a3cb-005056011aef 10 | ].freeze 11 | 12 | def page_is_on_exemption_list? 13 | EXEMPTION_LIST.include? content_id 14 | end 15 | 16 | def display_single_page_notification_button? 17 | !page_is_on_exemption_list? && locale == "en" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/concerns/withdrawable.rb: -------------------------------------------------------------------------------- 1 | module Withdrawable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | def withdrawn? 6 | withdrawal_notice.present? 7 | end 8 | 9 | def withdrawn_at 10 | withdrawal_notice["withdrawn_at"] 11 | end 12 | 13 | def withdrawn_explanation 14 | withdrawal_notice["explanation"] 15 | end 16 | end 17 | 18 | private 19 | 20 | def withdrawal_notice 21 | content_store_response["withdrawn_notice"] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/concerns/worldwide_organisations.rb: -------------------------------------------------------------------------------- 1 | module WorldwideOrganisations 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | def worldwide_organisations 6 | linked("worldwide_organisations") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/content_item_factory.rb: -------------------------------------------------------------------------------- 1 | class ContentItemFactory 2 | def self.build(content_store_response) 3 | schema_name = if content_store_response["document_type"] == "licence_transaction" 4 | content_store_response["document_type"] 5 | else 6 | content_store_response["schema_name"] 7 | end 8 | 9 | content_item_class(schema_name).new(content_store_response) 10 | end 11 | 12 | def self.content_item_class(schema_name) 13 | klass = schema_name.camelize.constantize 14 | klass.superclass == ContentItem ? klass : ContentItem 15 | rescue StandardError 16 | ContentItem 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/contents_outline.rb: -------------------------------------------------------------------------------- 1 | class ContentsOutline 2 | attr_reader :items 3 | 4 | Item = Data.define(:text, :id, :level, :items) 5 | 6 | def initialize(headers_array) 7 | @items = headers_array_to_items(headers_array) 8 | end 9 | 10 | def level_two_headers? 11 | nested_level_two_headers?(items) 12 | end 13 | 14 | private 15 | 16 | def headers_array_to_items(headers_array) 17 | return [] if headers_array.blank? 18 | 19 | headers_array.map do |header| 20 | items = headers_array_to_items(header["headers"]) 21 | Item.new( 22 | text: header["text"], 23 | id: header["id"], 24 | level: header["level"], 25 | items:, 26 | ) 27 | end 28 | end 29 | 30 | def nested_level_two_headers?(items_array) 31 | items_array.detect { |item| item.level == 2 || nested_level_two_headers?(item.items) }.present? 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/models/detailed_guide.rb: -------------------------------------------------------------------------------- 1 | class DetailedGuide < ContentItem 2 | include SinglePageNotificationButton 3 | end 4 | -------------------------------------------------------------------------------- /app/models/election_postcode.rb: -------------------------------------------------------------------------------- 1 | class ElectionPostcode 2 | UK_POSTCODE_PATTERN = %r{ 3 | \A 4 | # Outward code, for example SW1A 5 | (([A-Z][0-9]{1,2})|(([A-Z][A-HJ-Y][0-9]{1,2})|(([A-Z][0-9][A-Z])|([A-Z][A-HJ-Y][0-9][A-Z]?)))) 6 | \s? 7 | [0-9][A-Z]{2} # Inward code, for example 2AA 8 | \Z 9 | }xi 10 | 11 | delegate :present?, to: :sanitized_postcode 12 | 13 | def initialize(postcode) 14 | @postcode = postcode 15 | end 16 | 17 | def sanitized_postcode 18 | @sanitized_postcode ||= PostcodeSanitizer.sanitize(postcode) 19 | end 20 | 21 | def postcode_for_api 22 | sanitized_postcode.gsub(/\s+/, "") 23 | end 24 | 25 | def valid? 26 | return false unless sanitized_postcode 27 | 28 | sanitized_postcode.match?(UK_POSTCODE_PATTERN) 29 | end 30 | 31 | def error 32 | "invalidPostcodeFormat" unless valid? 33 | end 34 | 35 | private 36 | 37 | attr_reader :postcode 38 | end 39 | -------------------------------------------------------------------------------- /app/models/fatality_notice.rb: -------------------------------------------------------------------------------- 1 | class FatalityNotice < ContentItem 2 | include EmphasisedOrganisations 3 | include Updatable 4 | 5 | def field_of_operation 6 | linked("field_of_operation").first 7 | end 8 | 9 | def contributors 10 | organisations_ordered_by_emphasis + linked("people") 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/field_of_operation.rb: -------------------------------------------------------------------------------- 1 | class FieldOfOperation < ContentItem 2 | def fatality_notices 3 | linked("fatality_notices") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/fields_of_operation.rb: -------------------------------------------------------------------------------- 1 | class FieldsOfOperation < ContentItem 2 | def fields_of_operation 3 | linked("fields_of_operation") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/finder.rb: -------------------------------------------------------------------------------- 1 | class Finder < ContentItem 2 | attr_reader :facets, :show_metadata_block, :show_table_of_contents 3 | 4 | def initialize(content_store_response) 5 | super(content_store_response) 6 | 7 | @facets = content_store_response.dig("details", "facets") 8 | @show_metadata_block = content_store_response.dig("details", "show_metadata_block") 9 | @show_table_of_contents = content_store_response.dig("details", "show_table_of_contents") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/flexible_page.rb: -------------------------------------------------------------------------------- 1 | class FlexiblePage < ContentItem 2 | attr_reader :flexible_sections 3 | 4 | def initialize(content_store_response) 5 | super 6 | 7 | @flexible_sections = (content_store_response.dig("details", "flexible_sections") || []).map { |hash| FlexiblePage::FlexibleSectionFactory.build(hash, self) } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/flexible_page/flexible_section/base.rb: -------------------------------------------------------------------------------- 1 | module FlexiblePage::FlexibleSection 2 | class Base 3 | attr_reader :flexible_section_hash, :content_item, :type 4 | 5 | def initialize(flexible_section_hash, content_item) 6 | @flexible_section_hash = flexible_section_hash 7 | @type = flexible_section_hash["type"] 8 | @content_item = content_item 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/flexible_page/flexible_section/page_title.rb: -------------------------------------------------------------------------------- 1 | module FlexiblePage::FlexibleSection 2 | class PageTitle < Base 3 | attr_reader :context, :heading_text, :lead_paragraph 4 | 5 | def initialize(flexible_section_hash, content_item) 6 | super 7 | 8 | @context = flexible_section_hash["context"] 9 | @heading_text = flexible_section_hash["heading_text"] 10 | @lead_paragraph = flexible_section_hash["lead_paragraph"] 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/flexible_page/flexible_section/rich_content.rb: -------------------------------------------------------------------------------- 1 | module FlexiblePage::FlexibleSection 2 | class RichContent < Base 3 | ContentImage = Data.define(:alt, :src) 4 | 5 | attr_reader :contents_list, :govspeak, :image 6 | 7 | def initialize(flexible_section_hash, content_item) 8 | super 9 | 10 | @contents_list = ContentsOutline.new(flexible_section_hash["contents_list"]) 11 | @govspeak = flexible_section_hash["govspeak"] 12 | 13 | if flexible_section_hash["image"].present? 14 | alt, src = flexible_section_hash.fetch("image").values_at("alt", "src") 15 | @image = ContentImage.new(alt:, src:) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/flexible_page/flexible_section_factory.rb: -------------------------------------------------------------------------------- 1 | class FlexiblePage::FlexibleSectionFactory 2 | def self.build(flexible_section_hash, content_item) 3 | section_class(flexible_section_hash["type"]).new(flexible_section_hash, content_item) 4 | end 5 | 6 | def self.section_class(type) 7 | "FlexiblePage::FlexibleSection::#{type.camelize}".constantize 8 | rescue StandardError 9 | raise("Couldn't identify a model class for type: #{type}") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/homepage.rb: -------------------------------------------------------------------------------- 1 | class Homepage < ContentItem 2 | def popular_links 3 | @popular_links ||= popular_links_data&.collect(&:with_indifferent_access) 4 | end 5 | 6 | private 7 | 8 | def popular_links_data 9 | @popular_links_data ||= links.dig("popular_links", 0, "details", "link_items") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/landing_page.rb: -------------------------------------------------------------------------------- 1 | class LandingPage < ContentItem 2 | attr_reader :blocks, :navigation_groups, :breadcrumbs, :theme 3 | 4 | ADDITIONAL_CONTENT_PATH = "lib/data/landing_page_content_items".freeze 5 | 6 | def initialize(content_store_response) 7 | super 8 | 9 | @breadcrumbs = content_store_response.dig("details", "breadcrumbs")&.map { { title: _1["title"], url: _1["href"] } } 10 | @navigation_groups = (content_store_response.dig("details", "navigation_groups") || []).index_by { _1["id"] } 11 | @blocks = (content_store_response.dig("details", "blocks") || []).map { |block_hash| BlockFactory.build(block_hash, self) } 12 | @theme = safe_theme(content_store_response.dig("details", "theme")) 13 | end 14 | 15 | private 16 | 17 | def safe_theme(value) 18 | return value if value == "prime-ministers-office-10-downing-street" 19 | 20 | "default" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/landing_page/block/action_link.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class ActionLink < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/landing_page/block/base.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class Base 3 | attr_reader :id, :data, :landing_page, :type 4 | 5 | def initialize(block_hash, landing_page) 6 | @data = block_hash 7 | @id = data["id"] 8 | @landing_page = landing_page 9 | @type = data["type"] 10 | end 11 | 12 | def full_width? 13 | false 14 | end 15 | 16 | def full_width_background? 17 | @data["full_width_background"] 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/landing_page/block/block_error.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class BlockError < Base 3 | attr_reader :error 4 | 5 | def initialize(block_hash, landing_page) 6 | super 7 | 8 | @error = data["error"] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/landing_page/block/blocks_container.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class BlocksContainer < LayoutBase 3 | alias_method :children, :blocks 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/landing_page/block/box.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class Box < Base 3 | attr_reader :box_content, :href, :content 4 | 5 | def initialize(block_hash, landing_page) 6 | super 7 | 8 | @href = data["href"] || "" 9 | @content = data["content"] || "" 10 | 11 | @box_content = LandingPage::BlockFactory.build_all(data.dig("box_content", "blocks"), landing_page) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/landing_page/block/card.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | CardImage = Data.define(:alt, :source) 3 | 4 | class Card < Base 5 | attr_reader :image, :card_content, :href, :content 6 | 7 | def initialize(block_hash, landing_page) 8 | super 9 | 10 | @href = data["href"] || "" 11 | @content = data["content"] || "" 12 | 13 | if data["image"].present? 14 | alt, source = data.fetch("image").values_at("alt", "source") 15 | @image = CardImage.new(alt:, source:) 16 | end 17 | 18 | @card_content = LandingPage::BlockFactory.build_all(data.dig("card_content", "blocks"), landing_page) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/landing_page/block/columns_layout.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class ColumnsLayout < LayoutBase 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/landing_page/block/featured.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | FeaturedImageSources = Data.define(:desktop, :desktop_2x, :tablet, :tablet_2x, :mobile, :mobile_2x) 3 | FeaturedImage = Data.define(:alt, :sources) 4 | 5 | class Featured < Base 6 | attr_reader :image, :featured_content 7 | 8 | def initialize(block_hash, landing_page) 9 | super 10 | 11 | if data["image"].present? 12 | alt, sources = data.fetch("image").values_at("alt", "sources") 13 | sources = FeaturedImageSources.new(**sources) 14 | @image = FeaturedImage.new(alt:, sources:) 15 | end 16 | 17 | @featured_content = data.dig("featured_content", "blocks")&.map { |subblock_hash| LandingPage::BlockFactory.build(subblock_hash, landing_page) } 18 | end 19 | 20 | def full_width? 21 | false 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/landing_page/block/govspeak.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class Govspeak < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/landing_page/block/heading.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class Heading < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/landing_page/block/image.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | ImageSources = Data.define(:desktop, :desktop_2x, :tablet, :tablet_2x, :mobile, :mobile_2x) 3 | ImageData = Data.define(:alt, :sources) 4 | 5 | class Image < Base 6 | attr_reader :image 7 | 8 | def initialize(block_hash, landing_page) 9 | super 10 | 11 | if data["image"].present? 12 | image_config = data.fetch("image") 13 | alt = image_config.fetch("alt", "") 14 | sources = image_config.fetch("sources") 15 | sources = ImageSources.new(**sources) 16 | @image = ImageData.new(alt:, sources:) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/landing_page/block/layout_base.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class LayoutBase < Base 3 | attr_reader :blocks 4 | 5 | def initialize(block_hash, landing_page) 6 | super 7 | 8 | @blocks = LandingPage::BlockFactory.build_all(data["blocks"], landing_page) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/landing_page/block/logo.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class Logo < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/landing_page/block/main_navigation.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class MainNavigation < Base 3 | attr_reader :name, :links 4 | 5 | def initialize(block_hash, landing_page) 6 | super 7 | 8 | nav_group_id = data["navigation_group_id"] 9 | raise "Main Navigation block points to a missing navigation group: #{nav_group_id}" unless landing_page.navigation_groups.key?(nav_group_id) 10 | 11 | navigation_group = landing_page.navigation_groups[nav_group_id] 12 | @name = navigation_group["name"] 13 | @links = navigation_group["links"] 14 | end 15 | 16 | def full_width? 17 | true 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/landing_page/block/quote.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class Quote < Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/landing_page/block/share_links.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class ShareLinks < Base 3 | attr_reader :links 4 | 5 | def initialize(block_hash, landing_page) 6 | super 7 | 8 | @links = data.fetch("links").map { |l| { href: l["href"], text: l["text"], icon: l["icon"], hidden_text: l["hidden_text"] } } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/landing_page/block/side_navigation.rb: -------------------------------------------------------------------------------- 1 | module LandingPage::Block 2 | class SideNavigation < Base 3 | attr_reader :links 4 | 5 | def initialize(block_hash, landing_page) 6 | super 7 | 8 | nav_group_id = data["navigation_group_id"] 9 | raise "Side Navigation block points to a missing navigation group: #{nav_group_id}" unless landing_page.navigation_groups.key?(nav_group_id) 10 | 11 | @links = landing_page.navigation_groups[nav_group_id]["links"] 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/landing_page/block_factory.rb: -------------------------------------------------------------------------------- 1 | class LandingPage::BlockFactory 2 | def self.build_all(block_array, landing_page) 3 | (block_array || []).map { |block| build(block, landing_page) } 4 | end 5 | 6 | def self.build(block_hash, landing_page) 7 | block_class(block_hash["type"]).new(block_hash, landing_page) 8 | rescue StandardError => e 9 | LandingPage::Block::BlockError.new({ "type" => "block_error", "error" => e }, landing_page) 10 | end 11 | 12 | def self.block_class(type) 13 | "LandingPage::Block::#{type.camelize}".constantize 14 | rescue StandardError 15 | raise("Couldn't identify a model class for type: #{type}") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/models/licence_transaction.rb: -------------------------------------------------------------------------------- 1 | class LicenceTransaction < ContentItem 2 | attr_reader :body, 3 | :licence_transaction_continuation_link, 4 | :licence_transaction_licence_identifier, 5 | :licence_transaction_will_continue_on 6 | 7 | def initialize(content_store_response) 8 | super(content_store_response) 9 | 10 | @body = content_store_response.dig("details", "body") 11 | @licence_transaction_continuation_link = content_store_response.dig("details", "metadata", "licence_transaction_continuation_link") 12 | @licence_transaction_licence_identifier = content_store_response.dig("details", "metadata", "licence_transaction_licence_identifier") 13 | @licence_transaction_will_continue_on = content_store_response.dig("details", "metadata", "licence_transaction_will_continue_on") 14 | end 15 | 16 | def slug 17 | URI.parse(base_path).path.split("/").last 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/locations_api_postcode_response.rb: -------------------------------------------------------------------------------- 1 | class LocationsApiPostcodeResponse 2 | attr_reader :local_custodian_codes, :error, :postcode 3 | 4 | def initialize(postcode, local_custodian_codes, error) 5 | @postcode = postcode 6 | @local_custodian_codes = local_custodian_codes 7 | @error = error 8 | end 9 | 10 | def location_not_found? 11 | postcode && local_custodian_codes.empty? && error.nil? 12 | end 13 | 14 | def invalid_postcode? 15 | postcode && local_custodian_codes.nil? && error.present? 16 | end 17 | 18 | def blank_postcode? 19 | postcode.blank? 20 | end 21 | 22 | def single_authority? 23 | local_custodian_codes.count == 1 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/logo.rb: -------------------------------------------------------------------------------- 1 | class Logo 2 | attr_reader :crest, :formatted_title, :image 3 | 4 | def initialize(logo) 5 | @image = get_image(logo["image"]) 6 | @crest = logo["crest"] 7 | @formatted_title = logo["formatted_title"] 8 | end 9 | 10 | def get_image(logo_image) 11 | return unless logo_image 12 | 13 | OpenStruct.new( 14 | alt_text: logo_image["alt_text"], 15 | url: logo_image["url"], 16 | ) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/news_article.rb: -------------------------------------------------------------------------------- 1 | class NewsArticle < ContentItem 2 | include EmphasisedOrganisations 3 | include NewsImage 4 | include People 5 | include Political 6 | include Updatable 7 | include Withdrawable 8 | include WorldwideOrganisations 9 | 10 | def contributors 11 | (organisations_ordered_by_emphasis + worldwide_organisations + people).uniq 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/organisation.rb: -------------------------------------------------------------------------------- 1 | class Organisation < ContentItem 2 | attr_reader :logo 3 | 4 | def initialize(organisation_data) 5 | super(organisation_data) 6 | @logo = Logo.new(organisation_data.dig("details", "logo")) 7 | @organisation_data = organisation_data 8 | end 9 | 10 | def brand 11 | organisation_data.dig("details", "brand") 12 | end 13 | 14 | private 15 | 16 | attr_reader :organisation_data 17 | end 18 | -------------------------------------------------------------------------------- /app/models/place.rb: -------------------------------------------------------------------------------- 1 | class Place < ContentItem 2 | attr_reader :introduction, :more_information, :need_to_know, :place_type 3 | 4 | def initialize(content_store_response) 5 | super(content_store_response) 6 | 7 | @introduction = content_store_response.dig("details", "introduction") 8 | @more_information = content_store_response.dig("details", "more_information") 9 | @need_to_know = content_store_response.dig("details", "need_to_know") 10 | @place_type = content_store_response.dig("details", "place_type") 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/record_not_found.rb: -------------------------------------------------------------------------------- 1 | class RecordNotFound < StandardError 2 | end 3 | -------------------------------------------------------------------------------- /app/models/service_manual_homepage.rb: -------------------------------------------------------------------------------- 1 | class ServiceManualHomepage < ContentItem 2 | def topics 3 | linked("children") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/service_manual_service_toolkit.rb: -------------------------------------------------------------------------------- 1 | class ServiceManualServiceToolkit < ContentItem 2 | attr_reader :collections 3 | 4 | def initialize(content_store_response) 5 | super(content_store_response) 6 | 7 | @collections = content_store_response["details"].fetch("collections", []) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/simple_smart_answer.rb: -------------------------------------------------------------------------------- 1 | class SimpleSmartAnswer < ContentItem 2 | attr_reader :body, :nodes, :start_button_text 3 | 4 | def initialize(content_store_response) 5 | super(content_store_response) 6 | 7 | @body = content_store_response.dig("details", "body") 8 | @nodes = content_store_response.dig("details", "nodes") 9 | @start_button_text = content_store_response.dig("details", "start_button_text") 10 | end 11 | 12 | def slug 13 | @slug = URI.parse(@base_path).path.sub(%r{\A/}, "") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/speech.rb: -------------------------------------------------------------------------------- 1 | class Speech < ContentItem 2 | include EmphasisedOrganisations 3 | include NewsImage 4 | include People 5 | include Political 6 | include Updatable 7 | 8 | def contributors 9 | (organisations_ordered_by_emphasis + people + speaker).compact.uniq(&:content_id) 10 | end 11 | 12 | def speaker 13 | linked("speaker") 14 | end 15 | 16 | def speaker_without_profile 17 | content_store_response.dig("details", "speaker_without_profile") 18 | end 19 | 20 | def location 21 | content_store_response.dig("details", "location") 22 | end 23 | 24 | def delivered_on_date 25 | content_store_response.dig("details", "delivered_on") 26 | end 27 | 28 | def speech_type_explanation 29 | explanation = content_store_response.dig("details", "speech_type_explanation") 30 | " (#{explanation})" if explanation 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/models/uprn.rb: -------------------------------------------------------------------------------- 1 | class Uprn 2 | UPRN_PATTERN = %r{^\d{1,12}$} 3 | 4 | delegate :present?, to: :sanitized_uprn 5 | 6 | def initialize(uprn) 7 | @uprn = uprn || "" 8 | end 9 | 10 | def valid? 11 | sanitized_uprn.match?(UPRN_PATTERN) 12 | end 13 | 14 | def sanitized_uprn 15 | uprn.strip 16 | end 17 | 18 | def error 19 | "invalidUprnFormat" unless valid? 20 | end 21 | 22 | private 23 | 24 | attr_reader :uprn 25 | end 26 | -------------------------------------------------------------------------------- /app/presenters/address_list_presenter.rb: -------------------------------------------------------------------------------- 1 | class AddressListPresenter 2 | def initialize(addresses) 3 | @addresses = addresses 4 | end 5 | 6 | def component_options 7 | addresses_with_authority_data.map { |address| { text: address[:address], value: address[:local_authority_slug] } } 8 | end 9 | 10 | def addresses_with_authority_data 11 | @addresses_with_authority_data ||= @addresses.map do |address| 12 | local_authority = LocalAuthority.from_local_custodian_code(address.local_custodian_code) 13 | { 14 | address: address.address, 15 | local_authority_slug: local_authority.slug, 16 | local_authority_name: local_authority.name, 17 | } 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/presenters/content_item_presenter.rb: -------------------------------------------------------------------------------- 1 | class ContentItemPresenter 2 | attr_reader :content_item 3 | 4 | def initialize(content_item) 5 | @content_item = content_item 6 | end 7 | 8 | def contributor_links 9 | content_item.contributors.map do |content_item| 10 | { text: content_item.title, path: content_item.base_path } 11 | end 12 | end 13 | 14 | def page_title 15 | content_item.withdrawn? ? "[Withdrawn] #{content_item.title}" : content_item.title 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/presenters/contents_outline_presenter.rb: -------------------------------------------------------------------------------- 1 | class ContentsOutlinePresenter 2 | attr_reader :contents_outline 3 | 4 | def initialize(contents_outline) 5 | @contents_outline = contents_outline 6 | end 7 | 8 | def for_contents_list_component 9 | items_to_component_h(contents_outline.items) 10 | end 11 | 12 | private 13 | 14 | def items_to_component_h(items) 15 | items.map do |item| 16 | { 17 | href: "##{item.id}", 18 | text: item.text.gsub(/:$/, ""), 19 | items: items_to_component_h(item.items), 20 | } 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/presenters/local_authority_presenter.rb: -------------------------------------------------------------------------------- 1 | class LocalAuthorityPresenter 2 | def initialize(local_authority) 3 | @local_authority = local_authority 4 | end 5 | 6 | PASS_THROUGH_KEYS = %i[ 7 | name 8 | snac 9 | gss 10 | tier 11 | homepage_url 12 | country_name 13 | ].freeze 14 | 15 | PASS_THROUGH_KEYS.each do |key| 16 | define_method key do 17 | local_authority[key.to_s] 18 | end 19 | end 20 | 21 | def url 22 | @url ||= extract_first_url 23 | end 24 | 25 | private 26 | 27 | attr_accessor :local_authority 28 | 29 | def extract_first_url 30 | homepage_url.presence || nil 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/presenters/place_presenter.rb: -------------------------------------------------------------------------------- 1 | class PlacePresenter < ContentItemPresenter 2 | attr_reader :places 3 | 4 | def initialize(content_item, places = []) 5 | super(content_item) 6 | @places = format_places(places) 7 | end 8 | 9 | def preposition 10 | return "near" unless @places.any? 11 | 12 | @places.first["gss"].blank? ? "near" : "for" 13 | end 14 | 15 | private 16 | 17 | def format_places(places) 18 | places.each do |place| 19 | place["text"] = place["url"] if place["url"] 20 | place["address"] = place 21 | .values_at("address1", "address2") 22 | .compact 23 | .map(&:strip) 24 | .join(", ") 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/presenters/service_manual_homepage_presenter.rb: -------------------------------------------------------------------------------- 1 | class ServiceManualHomepagePresenter < ContentItemPresenter 2 | def sorted_topics 3 | content_item.topics.sort_by(&:title).map do |topic| 4 | { 5 | link: { 6 | path: topic.base_path, 7 | text: topic.title, 8 | }, 9 | description: topic.description, 10 | } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/presenters/simple_smart_answer_presenter.rb: -------------------------------------------------------------------------------- 1 | class SimpleSmartAnswerPresenter < ContentItemPresenter 2 | def start_button_text 3 | if content_item.start_button_text == "Start now" 4 | I18n.t("formats.start_now") 5 | elsif content_item.start_button_text == "Continue" 6 | I18n.t("continue") 7 | else 8 | content_item.start_button_text 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/presenters/speech_presenter.rb: -------------------------------------------------------------------------------- 1 | class SpeechPresenter < ContentItemPresenter 2 | def speech_contributor_links 3 | return contributor_links unless content_item.speaker_without_profile 4 | 5 | contributor_links + [{ text: content_item.speaker_without_profile }] 6 | end 7 | 8 | def delivery_type 9 | return I18n.t("formats.speech.written_on") if content_item.document_type == "authored_article" 10 | 11 | I18n.t("formats.speech.delivered_on") 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/presenters/transaction_presenter.rb: -------------------------------------------------------------------------------- 1 | class TransactionPresenter < ContentItemPresenter 2 | def start_button_text 3 | if content_item.start_button_text.blank? 4 | return I18n.t("formats.start_now") 5 | end 6 | 7 | case content_item.start_button_text 8 | when "Start now" 9 | I18n.t("formats.start_now") 10 | when "Sign in" 11 | I18n.t("formats.transaction.sign_in") 12 | else 13 | content_item.start_button_text 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/application/_draft_fields.html.erb: -------------------------------------------------------------------------------- 1 | <% if @edition %> 2 | 3 | <% end %> 4 | 5 | <% if params[:token] %> 6 | 7 | <% end %> 8 | 9 | <% if params[:cache] %> 10 | 11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/calendar/_calendar_footer.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/views/calendar/_calendar_head.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "#{calendar.title} - GOV.UK" %> 2 | <% content_for :extra_headers do %> 3 | 4 | 5 | 6 | <% calendar.divisions.each do |division| %> 7 | 8 | 9 | <% end %> 10 | 11 | <%= render "govuk_publishing_components/components/machine_readable_metadata", 12 | schema: :article, 13 | content_item: content_item.to_h %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /app/views/components/_download_link.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_app_component_stylesheet("download-link") 3 | 4 | text ||= nil 5 | href ||= nil 6 | icon ||= "download" 7 | 8 | link_data_attributes ||= false 9 | 10 | component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns) 11 | component_helper.add_class("govuk-body govuk-link govuk-!-font-weight-bold govuk-!-display-none-print") 12 | component_helper.add_class("app-c-download-link app-c-download-link--#{icon}") 13 | component_helper.add_data_attribute(link_data_attributes) if link_data_attributes 14 | %> 15 | <% if text && href %> 16 | <%= link_to text, href, **component_helper.all_attributes %> 17 | <% end %> 18 | -------------------------------------------------------------------------------- /app/views/csv_preview/access_limited.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "#{I18n.t('csv_preview.access_limited.title')} - GOV.UK" %> 2 | 3 |
4 |
5 |
6 |

7 | <%= I18n.t("csv_preview.access_limited.body") %> 8 |

9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /app/views/electoral/_contact_details.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "govuk_publishing_components/components/heading", 2 | text: title, 3 | font_size: "m", 4 | margin_bottom: 4 %> 5 | <% if name %> 6 |

<%= name %>

7 | <% end %> 8 | 9 |

<%= description %>

10 | 11 | <% if contact_details %> 12 | <%= render "govuk_publishing_components/components/govspeak", { 13 | } do %> 14 |
15 | <% contact_details.each do |address_line| %> 16 | <%= address_line %>
17 | <% end %> 18 | <%= postcode %>
19 |
20 | <% end %> 21 | <% end %> 22 | 23 | <%= render "govuk_publishing_components/components/list", { 24 | items: [ 25 | sanitize(link_to website, website, class: "govuk-link"), 26 | phone, 27 | email, 28 | ], 29 | } %> 30 | -------------------------------------------------------------------------------- /app/views/fields_of_operation/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= render "govuk_publishing_components/components/heading", { 4 | text: content_item.title, 5 | context: I18n.t("formats.fields_of_operation.context"), 6 | font_size: "xl", 7 | margin_bottom: 8, 8 | heading_level: 1, 9 | lang: content_item.locale, 10 | } %> 11 | 12 |
13 |
14 |
    15 | <% content_item.fields_of_operation.each do |content_item| %> 16 | <%= link_to content_item.title, content_item.base_path, class: "govuk-link" %> 17 |
    18 | <% end %> 19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /app/views/find_local_council/_base_page.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :extra_headers do %> 2 | 4 | <% end %> 5 | 6 |
7 |
8 | <%= render "govuk_publishing_components/components/heading", { 9 | text: t("formats.local_transaction.find_council"), 10 | font_size: "xl", 11 | margin_bottom: 8, 12 | heading_level: 1, 13 | } %> 14 |
15 |
16 | <%= yield %> 17 |
18 |
19 | <%= render "govuk_publishing_components/components/contextual_sidebar", content_item: content_item.to_h %> 20 |
21 |
22 | -------------------------------------------------------------------------------- /app/views/find_local_council/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title do %><%= "Error: " if @location_error %>Find your local council - GOV.UK<% end %> 2 | 3 | <%= render layout: "base_page" do %> 4 |

<%= t("formats.local_transaction.find_council_website") %>

5 | <%= render partial: "location_form", 6 | locals: { 7 | format: "service", 8 | publication_format: "find_local_council", 9 | postcode: @postcode, 10 | margin_top: @location_error ? 5 : 0, 11 | publication_title: t("formats.local_transaction.find_council", locale: :en), 12 | } %> 13 | <% end %> 14 | -------------------------------------------------------------------------------- /app/views/flexible_page/flexible_sections/_page_title.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render "govuk_publishing_components/components/heading", { 3 | text: flexible_section.heading_text, 4 | context: flexible_section.context, 5 | heading_level: 1, 6 | font_size: "xl", 7 | margin_bottom: 8, 8 | } %> 9 | <%= render "govuk_publishing_components/components/lead_paragraph", { 10 | text: flexible_section.lead_paragraph, 11 | } %> 12 |
13 | -------------------------------------------------------------------------------- /app/views/flexible_page/flexible_sections/_rich_content.html.erb: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | <%= render "govuk_publishing_components/components/govspeak", {} do %> 13 | <%= flexible_section.govspeak.html_safe %> 14 | <% end %> 15 |
16 | -------------------------------------------------------------------------------- /app/views/flexible_page/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :extra_headers do %> 2 | 3 | 4 | <%= render "govuk_publishing_components/components/machine_readable_metadata", { 5 | content_item: content_item.to_h, 6 | schema: :article, 7 | } %> 8 | <% end %> 9 | 10 | <% content_for :before_content do %> 11 |
12 | <%= render "govuk_publishing_components/components/contextual_breadcrumbs", content_item: content_item.to_h %> 13 |
14 | <% end %> 15 | 16 |
17 | <% content_item.flexible_sections.each do |flexible_section| %> 18 |
19 | <%= render("flexible_page/flexible_sections/#{flexible_section.type}", flexible_section:) %> 20 |
21 | <% end %> 22 |
23 | -------------------------------------------------------------------------------- /app/views/help/ab_testing.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "A/B testing - GOV.UK" %> 2 | <% content_for :extra_headers do %> 3 | <%= @requested_variant.analytics_meta_tag.html_safe %> 4 | 5 | <% end %> 6 | 7 |
8 |
9 | <%= render "govuk_publishing_components/components/heading", { 10 | text: "This is a test page", 11 | heading_level: 1, 12 | font_size: "xl", 13 | margin_bottom: 8, 14 | } %> 15 | 16 |

<%= t("ab_testing.explanation") %>

17 | 18 |

<%= t("ab_testing.two_versions") %> <%= @requested_variant.variant?("A") ? t("a") : t("b") %> <%= t("ab_testing.version") %>.

19 | 20 |

<%= t("ab_testing.visit_govuk_html") %>

21 |
22 |
23 | -------------------------------------------------------------------------------- /app/views/help_page/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :extra_headers do %> 2 | <%= render "govuk_publishing_components/components/machine_readable_metadata", { content_item: @content_item.to_h, schema: :article } %> 3 | 4 | <% if @content_item.base_path == '/help/cookie-details' %> 5 | 6 | <% end %> 7 | <% end %> 8 | 9 | <%= render "shared/body_with_related_links" %> 10 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_action_link.html.erb: -------------------------------------------------------------------------------- 1 | <% inverse = options[:inverse] || false %> 2 | <%= render "govuk_publishing_components/components/action_link", { 3 | inverse: inverse, 4 | text: block.data["text"], 5 | href: block.data["href"], 6 | } %> 7 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_block_error.html.erb: -------------------------------------------------------------------------------- 1 | <% if draft_host? %> 2 | <% 3 | add_view_stylesheet("landing_page/block-error") 4 | %> 5 |
6 | <%= render "govuk_publishing_components/components/heading", { 7 | text: block.error.message, 8 | heading_level: 3, 9 | } %> 10 |
11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_blocks_container.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% block.children.each do |child| %> 3 | <%= render_block(child) %> 4 | <% end %> 5 |
6 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_box.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("landing_page/box") 3 | %> 4 |
"> 5 | <%= render "govuk_publishing_components/components/heading", { 6 | text: govuk_styled_link(block.content, path: block.href), 7 | heading_level: 2, 8 | margin_bottom: 4, 9 | } %> 10 | 11 | <% block.box_content.each do |subblock| %> 12 | <%= render_block(subblock) %> 13 | <% end %> 14 |
15 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_card.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("landing_page/card") 3 | %> 4 |
"> 5 |
6 | <%= render "govuk_publishing_components/components/heading", { 7 | text: govuk_styled_link(block.content, path: block.href), 8 | heading_level: 2, 9 | } %> 10 |
11 | 12 | <% block.card_content.each do |subblock| %> 13 | <%= render_block(subblock) %> 14 | <% end %> 15 | 16 | <% if block.image %> 17 |
18 | <%= image_tag(block.image.source, alt: block.image.alt, class: "card__image") %> 19 |
20 | <% end %> 21 |
22 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_columns_layout.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("landing_page/columns_layout") 3 | 4 | data_attributes = { 5 | columns: block.data["columns"] || 3, 6 | } 7 | 8 | ga4_tracking = block.data["ga4_tracking"] || false 9 | 10 | if ga4_tracking 11 | data_attributes.merge!({ 12 | module: "ga4-link-tracker", 13 | ga4_set_indexes: "", 14 | ga4_track_links_only: "", 15 | ga4_link: { 16 | event_name: "navigation", 17 | type: block.data["ga4_type"], 18 | }.to_json, 19 | }) 20 | end 21 | %> 22 | 23 | <%= tag.div class: "columns-layout", data: data_attributes do %> 24 | <% block.blocks.each { |child| %><%= render_block(child) %><% } %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_document_list.html.erb: -------------------------------------------------------------------------------- 1 | <% heading = block.heading || "Latest Updates" %> 2 | 3 | <% if block.items.any? %> 4 | <%= render "govuk_publishing_components/components/heading", { 5 | text: heading, 6 | padding: true, 7 | border_top: 5, 8 | margin_bottom: 3, 9 | } %> 10 | 11 | <%= render "govuk_publishing_components/components/document_list", { 12 | margin_bottom: 0, 13 | items: block.items, 14 | ga4_extra_data: { 15 | section: heading, 16 | }, 17 | } %> 18 | <% end %> 19 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_govspeak.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | inverse = options[:inverse] || false 3 | margin_bottom = options[:margin_bottom] || 0 4 | %> 5 | <%= render "govuk_publishing_components/components/govspeak", { 6 | inverse: inverse, 7 | margin_bottom:, 8 | } do %> 9 | <%= block.data["content"].html_safe %> 10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_heading.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | font_size = block.data["font_size"] || options[:font_size] 3 | heading_level = block.data["heading_level"] || 2 4 | inverse = options[:inverse] || false 5 | margin_bottom = options[:margin_bottom] || 6 6 | margin_bottom = 0 if block.full_width_background? 7 | path = block.data["path"] 8 | text = block.data["content"] 9 | %> 10 | <%= render "govuk_publishing_components/components/heading", { 11 | context: block.data["context"], 12 | font_size:, 13 | heading_level: , 14 | id: block.data["id"], 15 | inverse:, 16 | margin_bottom:, 17 | text: govuk_styled_link(text, path:, inverse:), 18 | } %> 19 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_image.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("landing_page/image") 3 | 4 | img_classes = [ 5 | "image", 6 | ("border-top--#{style(block.data["theme_colour"])}" if block.data["theme_colour"]), 7 | ] 8 | %> 9 | 10 | <%= image_tag( 11 | block.image.sources.desktop, 12 | alt: block.image.alt, 13 | srcset: { 14 | block.image.sources.desktop => "630w", 15 | block.image.sources.desktop_2x => "1260w", 16 | block.image.sources.tablet => "708w", 17 | block.image.sources.tablet_2x => "1416w", 18 | block.image.sources.mobile => "610w", 19 | block.image.sources.mobile_2x => "1220w", 20 | }, 21 | sizes: [ 22 | "(max-width: 640px) 610px", 23 | "(max-width: 768px) 707px", 24 | "630px", 25 | ].join(","), 26 | class: img_classes, 27 | ) %> 28 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_logo.html.erb: -------------------------------------------------------------------------------- 1 | <% add_view_stylesheet("landing_page/logo") %> 2 | 7 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_share_links.html.erb: -------------------------------------------------------------------------------- 1 | <% if block.links.any? %> 2 | <%= render "govuk_publishing_components/components/heading", { 3 | text: "Our social media profiles", 4 | margin_bottom: 1, 5 | padding: true, 6 | border_top: 5, 7 | } %> 8 | 9 | <%= render "govuk_publishing_components/components/share_links", { 10 | square_icons: true, 11 | flexbox: true, 12 | links: block.links, 13 | track_as_follow: true, 14 | } %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_side_navigation.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("landing_page/side-navigation") 3 | %> 4 |
5 | <%= render "govuk_publishing_components/components/contents_list", { 6 | underline_links: block.data["underline_links"], 7 | alternative_line_style: block.data["alternative_line_style"], 8 | title: block.data["title"], 9 | contents: contents_list(request.path, block.links), 10 | } %> 11 |
12 | -------------------------------------------------------------------------------- /app/views/landing_page/blocks/_two_column_layout.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% if block.left.present? %> 4 | <%= render_block(block.left) %> 5 | <% end %> 6 |
7 | <% if block.right.present? %> 8 |
9 | <%= render_block(block.right) %> 10 |
11 | <% end %> 12 |
13 | -------------------------------------------------------------------------------- /app/views/landing_page/themes/_default.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :before_content do %> 2 |
3 |
4 |
5 |
6 | 7 |
8 | <%= yield :landing_page_breadcrumbs %> 9 |
10 |
11 |
12 | <% end %> 13 | 14 | <%= yield :landing_page_blocks %> 15 | -------------------------------------------------------------------------------- /app/views/licence_transaction/_authority_url.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "govuk_publishing_components/components/warning_text", { 2 | text: sanitize("To obtain this licence, you need to contact the authority directly. To continue, go to #{link_to(licence_details.authority["name"], licence_details.authority.dig("actions", locals[:action])[locals[:index]]["url"], class: "govuk-link")}."), 3 | } %> 4 | -------------------------------------------------------------------------------- /app/views/licence_transaction/_licensify_unavailable.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "#{content_item.title}: #{t('formats.local_transaction.contact_council')} - GOV.UK" %> 2 | 3 | <% 4 | ga4_attributes = { 5 | event_name: "form_complete", 6 | type: content_item.document_type.gsub("_", " "), 7 | text: "You cannot apply for this licence online", 8 | action: "complete", 9 | tool_name: content_item.title, 10 | }.to_json 11 | %> 12 |
15 | <%= render "govuk_publishing_components/components/warning_text", { 16 | text: sanitize("You cannot apply for this licence online. #{link_to('Contact your local council', '/find-local-council', class: "govuk-link")}."), 17 | } %> 18 |
19 | -------------------------------------------------------------------------------- /app/views/licence_transaction/authority.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "#{content_item.title}: #{licence_details.authority['name']} - GOV.UK" %> 2 | 3 | <% content_for :main_content do %> 4 | <%= render "govuk_publishing_components/components/govspeak", {} do %> 5 | <%= raw content_item.body %> 6 | <% end %> 7 | <% end %> 8 | 9 | <%= render "authority_base" %> 10 | -------------------------------------------------------------------------------- /app/views/licence_transaction/licence_not_found.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "shared/base_page", locals: { 2 | context: "Licence", 3 | title: content_item.title, 4 | publication: content_item, 5 | edition: @edition, 6 | } do %> 7 | 8 | <%= render "govuk_publishing_components/components/lead_paragraph", text: content_item.description %> 9 | 10 |
11 |
12 | <%= render partial: "licensify_unavailable" %> 13 | 14 |
15 | <%= render "govuk_publishing_components/components/govspeak", { 16 | } do %> 17 | <%= raw content_item.body %> 18 | <% end %> 19 |
20 |
21 |
22 | <% end %> 23 | -------------------------------------------------------------------------------- /app/views/local_transaction/_no_local_authority_url.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= t("formats.local_transaction.no_local_authority_url_html") %>

3 |
4 | -------------------------------------------------------------------------------- /app/views/placeholder/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, t("attachment.virus_check.intro") %> 2 | 3 |
4 | <%= render "govuk_publishing_components/components/heading", { 5 | text: t("attachment.virus_check.intro"), 6 | heading_level: 1, 7 | font_size: "xl", 8 | margin_bottom: 8, 9 | } %> 10 |

<%= t("attachment.virus_check.message") %>

11 |
12 | -------------------------------------------------------------------------------- /app/views/shared/_footer_navigation.html.erb: -------------------------------------------------------------------------------- 1 | <% @contextual_footer = capture do %> 2 | <%= render "govuk_publishing_components/components/contextual_footer", content_item: @content_item.to_h %> 3 | <% end %> 4 | <% if @contextual_footer.present? %> 5 |
6 |
7 | <%= @contextual_footer %> 8 |
9 |
10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/views/shared/_history_notice.html.erb: -------------------------------------------------------------------------------- 1 | <% if content_item.historically_political? %> 2 | <%= render "govuk_publishing_components/components/notice", { 3 | title: sanitize( 4 | t("shared.historically_political", government: "#{content_item.publishing_government}"), 5 | attributes: %w(lang dir), 6 | ), 7 | } %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /app/views/shared/_publication_metadata.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :extra_headers do %> 2 | <% if content_item.description.present? %> 3 | 4 | <% end %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/views/shared/_publisher_metadata.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("publisher_metadata") 3 | %> 4 | 5 |
6 |
7 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /app/views/shared/_sidebar_navigation.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | add_view_stylesheet("sidebar-navigation") 3 | %> 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/views/shared/_single_page_notification_button.html.erb: -------------------------------------------------------------------------------- 1 | <% if @content_item.display_single_page_notification_button? %> 2 | <% 3 | default_ga4_data_attributes = { 4 | module: "ga4-link-tracker", 5 | ga4_link: { 6 | event_name: "navigation", 7 | type: "subscribe", 8 | index_link: 1, 9 | index_total: 2, 10 | section: "Top", 11 | }, 12 | } 13 | %> 14 | 15 | <% ga4_data_attributes = ga4_data_attributes || default_ga4_data_attributes %> 16 | <% skip_account = skip_account || "false" %> 17 | 18 | <%= render "govuk_publishing_components/components/single_page_notification_button", { 19 | base_path: @content_item.base_path, 20 | js_enhancement: @has_govuk_account, 21 | ga4_data_attributes: ga4_data_attributes, 22 | margin_bottom: 6, 23 | button_location: "top", 24 | skip_account: skip_account, 25 | } %> 26 | <% end %> 27 | -------------------------------------------------------------------------------- /app/views/shared/_translations.html.erb: -------------------------------------------------------------------------------- 1 | <% if content_item.available_translations? %> 2 |
3 | <%= render "govuk_publishing_components/components/translation_nav", 4 | translations: translations_for_nav(content_item.available_translations) %> 5 |
6 | <% end %> 7 | -------------------------------------------------------------------------------- /app/views/simple_smart_answers/_outcome.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | content_for :title do 3 | simple_smart_answer_page_title(outcome.title) 4 | end 5 | 6 | ga4_attributes = { 7 | event_name: "form_complete", 8 | type: "simple smart answer", 9 | section: @flow_state.current_node.title, 10 | action: "complete", 11 | tool_name: content_item.title, 12 | text: outcome.slug, 13 | }.to_json 14 | %> 15 |
18 | <%= render "govuk_publishing_components/components/heading", { 19 | text: outcome.title, 20 | font_size: "l", 21 | margin_bottom: 4, 22 | heading_level: 1, 23 | context: content_item.title, 24 | } %> 25 | 26 | <% if outcome.body %> 27 | <%= render "govuk_publishing_components/components/govspeak", {} do %> 28 | <%= raw outcome.body %> 29 | <% end %> 30 | <% end %> 31 |
32 | -------------------------------------------------------------------------------- /app/views/static_error_pages/400.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, there is a problem", 3 | intro: '

Go back and change the information you entered.

', 4 | status_code: 400, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/401.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "You are not permitted to see this page", 3 | intro: '

This page is restricted to certain users only.

', 4 | status_code: 401, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/403.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "You are not permitted to see this page", 3 | intro: '

This page is restricted to certain users only.

', 4 | status_code: 403, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/404.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Page not found", 3 | intro: '

If you entered a web address, check it is correct.

4 |

You can browse from the homepage or use the search box above to find the information you need.

', 5 | status_code: 404, 6 | } %> 7 | -------------------------------------------------------------------------------- /app/views/static_error_pages/405.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "You are not allowed to do this action", 3 | intro: '

This page does not support the action you requested.

', 4 | status_code: 405, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/406.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "The page you’re looking for cannot be found", 3 | intro: '

If you entered a web address, check it is correct.

4 |

You can browse from the homepage or use the search box above to find the information you need.

', 5 | status_code: 406, 6 | } %> 7 | -------------------------------------------------------------------------------- /app/views/static_error_pages/410.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "The page you’re looking for is no longer here", 3 | intro: '

It may have been moved or replaced.

4 |

If you entered a web address, check it is correct.

5 |

You can browse from the homepage or use the search box above to find the information you need.

', 6 | status_code: 410, 7 | } %> 8 | -------------------------------------------------------------------------------- /app/views/static_error_pages/422.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, there is a problem", 3 | intro: '

Go back and change the information you entered.

', 4 | status_code: 422, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/429.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, there is a problem", 3 | intro: '

There have been too many attempts to access this page.

4 |

Try again later.

', 5 | status_code: 429, 6 | } %> 7 | -------------------------------------------------------------------------------- /app/views/static_error_pages/500.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, we’re experiencing technical difficulties", 3 | intro: '

Please try again in a few moments.

', 4 | status_code: 500, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/501.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, we’re experiencing technical difficulties", 3 | intro: '

Please try again in a few moments.

', 4 | status_code: 501, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/502.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, we’re experiencing technical difficulties", 3 | intro: '

Please try again in a few moments.

', 4 | status_code: 502, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/503.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, we’re experiencing technical difficulties", 3 | intro: '

Please try again in a few moments.

', 4 | status_code: 503, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/static_error_pages/504.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "error_page", locals: { 2 | heading: "Sorry, we’re experiencing technical difficulties", 3 | intro: '

Please try again in a few moments.

', 4 | status_code: 504, 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/travel_advice/_country.atom.builder: -------------------------------------------------------------------------------- 1 | feed.entry( 2 | country, 3 | id: country.feed_id, 4 | url: country.web_url, 5 | ) do |entry| 6 | entry.title(country.title) 7 | entry.link(rel: "self", type: "application/atom+xml", href: "#{country.web_url}.atom") 8 | entry.summary(type: :xhtml) do |summary| 9 | summary << format_atom_change_description(country.change_description) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/travel_advice/index.atom.builder: -------------------------------------------------------------------------------- 1 | atom_feed(root_url: travel_advice_url, id: travel_advice_url) do |feed| 2 | feed.title("Travel Advice Summary") 3 | feed.updated(@presenter.countries_by_date.first.updated_at) 4 | feed.author do |author| 5 | author.name "GOV.UK" 6 | end 7 | render partial: "country", collection: @presenter.countries_by_date.take(20), locals: { feed: } 8 | end 9 | -------------------------------------------------------------------------------- /app/views/travel_advice/show.atom.builder: -------------------------------------------------------------------------------- 1 | atom_feed(root_url: canonical_url(@content_item.base_path), id: canonical_url(@content_item.base_path)) do |feed| 2 | feed.title("Travel Advice Summary") 3 | feed.updated Time.zone.parse(@content_item.public_updated_at) 4 | feed.author do |author| 5 | author.name "GOV.UK" 6 | end 7 | feed.entry(@content_item, id: "#{canonical_url(@content_item.base_path)}##{Time.zone.parse(@content_item.public_updated_at)}", url: canonical_url(@content_item.base_path), updated: Time.zone.parse(@content_item.public_updated_at)) do |entry| 8 | entry.title(@content_item.title) 9 | entry.link(rel: "self", type: "application/atom+xml", href: "#{canonical_url(@content_item.base_path)}.atom") 10 | entry.summary(type: :xhtml) do |summary| 11 | summary << format_atom_change_description(@content_item.change_description) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 3 | load Gem.bin_path("bundler", "bundle") 4 | -------------------------------------------------------------------------------- /bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | if ! gem list foreman -i --silent; then 4 | echo "Installing foreman..." 5 | gem install foreman 6 | fi 7 | 8 | exec foreman start -f Procfile.dev "$@" 9 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "pathname" 3 | require "fileutils" 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path("..", __dir__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts "== Installing dependencies ==" 18 | system! "gem install bundler --conservative" 19 | system("bundle check") || system!("bundle install") 20 | 21 | puts "\n== Updating database ==" 22 | system! "bin/rails db:migrate" 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! "bin/rails log:clear tmp:clear" 26 | 27 | puts "\n== Restarting application server ==" 28 | system! "bin/rails restart" 29 | end 30 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 5 | select { |dir| File.expand_path(dir) != __dir__ }. 6 | product(["yarn", "yarn.cmd", "yarn.ps1"]). 7 | map { |dir, file| File.expand_path(file, dir) }. 8 | find { |file| File.executable?(file) } 9 | 10 | if yarn 11 | exec yarn, *ARGV 12 | else 13 | $stderr.puts "Yarn executable was not detected in the system." 14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 15 | exit 1 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path("../config/environment", __FILE__) 4 | run Frontend::Application 5 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/brakeman.ignore: -------------------------------------------------------------------------------- 1 | { 2 | "ignored_warnings": [ 3 | 4 | ], 5 | "updated": "2021-06-08 15:56:59 +0000", 6 | "brakeman_version": "5.0.2" 7 | } 8 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/i18n-tasks.yml: -------------------------------------------------------------------------------- 1 | data: 2 | yaml: 3 | write: 4 | line_width: -1 5 | ignore_unused: 6 | - 'formats.local_transaction.*' 7 | - 'formats.find_my_nearest.valid_postcode_no_locations' 8 | - 'common.nations.*' 9 | - 'common.substitute_day' 10 | - 'bank_holidays.*' 11 | - 'bank-holidays' 12 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Add Yarn node_modules folder to the asset load path. 8 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 9 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | GovukContentSecurityPolicy.configure 2 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/date_formats.rb: -------------------------------------------------------------------------------- 1 | Date::DATE_FORMATS[:short_ordinal] = "%d %B %Y" 2 | Time::DATE_FORMATS[:short_ordinal] = "%d %B %Y" 3 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += %i[ 7 | password passw email secret token _key crypt salt certificate otp ssn cvv cvc 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/govuk_publishing_components.rb: -------------------------------------------------------------------------------- 1 | if defined?(GovukPublishingComponents) 2 | GovukPublishingComponents.configure do |c| 3 | c.component_guide_title = "Frontend Component Guide" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/local_links_manager.rb: -------------------------------------------------------------------------------- 1 | require "gds_api/local_links_manager" 2 | 3 | Frontend.local_links_manager_api = GdsApi::LocalLinksManager.new(Plek.new.find("local-links-manager")) 4 | -------------------------------------------------------------------------------- /config/initializers/locations_api.rb: -------------------------------------------------------------------------------- 1 | require "gds_api/locations_api" 2 | require "plek" 3 | 4 | Frontend.locations_api = GdsApi::LocationsApi.new(Plek.new.find("locations-api")) 5 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | Mime::Type.register_alias "text/html", :video 7 | Mime::Type.register_alias "text/html", :raw 8 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /config/initializers/places_manager.rb: -------------------------------------------------------------------------------- 1 | require "gds_api/places_manager" 2 | 3 | Frontend.places_manager_api = GdsApi::PlacesManager.new(Plek.new.find("places-manager")) 4 | 5 | Frontend::PLACES_MANAGER_QUERY_LIMIT = 10 6 | -------------------------------------------------------------------------------- /config/initializers/prometheus.rb: -------------------------------------------------------------------------------- 1 | require "govuk_app_config/govuk_prometheus_exporter" 2 | GovukPrometheusExporter.configure 3 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: "_frontend_session" 4 | -------------------------------------------------------------------------------- /config/initializers/slimmer.rb: -------------------------------------------------------------------------------- 1 | Frontend::Application.configure do 2 | config.slimmer.logger = Rails.logger 3 | end 4 | -------------------------------------------------------------------------------- /config/initializers/website_root.rb: -------------------------------------------------------------------------------- 1 | require "plek" 2 | 3 | Frontend.govuk_website_root = Plek.new.website_root 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | require "govuk_app_config/govuk_puma" 2 | GovukPuma.configure_rails(self) 3 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 2 3 | :queues: 4 | - default 5 | - mailers 6 | -------------------------------------------------------------------------------- /docs/images/homepage-promotion-slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/docs/images/homepage-promotion-slots.png -------------------------------------------------------------------------------- /lib/api_error_routing_constraint.rb: -------------------------------------------------------------------------------- 1 | class ApiErrorRoutingConstraint 2 | def matches?(request) 3 | ContentItemLoader.for_request(request).load(request.path).is_a?(StandardError) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/data/specialist_documents/protected_food_drink_name/images.yaml: -------------------------------------------------------------------------------- 1 | protected-designation-of-origin-pdo: 2 | file_name: protected-designation-of-origin-pdo.png 3 | alt_text_tag: pdo_alt_text 4 | protected-geographical-indication-pgi: 5 | file_name: protected-geographical-indication-pgi.png 6 | alt_text_tag: pgi_alt_text 7 | traditional-speciality-guaranteed-tsg: 8 | file_name: traditional-speciality-guaranteed-tsg.png 9 | alt_text_tag: tsg_alt_text -------------------------------------------------------------------------------- /lib/format_routing_constraint.rb: -------------------------------------------------------------------------------- 1 | class FormatRoutingConstraint 2 | def initialize(format) 3 | @format = format 4 | end 5 | 6 | def matches?(request) 7 | content_item = ContentItemLoader.for_request(request).load(key(request)) 8 | content_item.is_a?(GdsApi::Response) && content_item["schema_name"] == @format 9 | end 10 | 11 | private 12 | 13 | def key(request) 14 | "/#{request.params.fetch(:slug)}" 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/frontend.rb: -------------------------------------------------------------------------------- 1 | module Frontend 2 | mattr_accessor :organisations_search_client 3 | mattr_accessor :search_client 4 | mattr_accessor :locations_api 5 | mattr_accessor :places_manager_api 6 | mattr_accessor :local_links_manager_api 7 | mattr_accessor :govuk_website_root 8 | end 9 | -------------------------------------------------------------------------------- /lib/full_path_format_routing_constraint.rb: -------------------------------------------------------------------------------- 1 | class FullPathFormatRoutingConstraint < FormatRoutingConstraint 2 | private 3 | 4 | def key(request) 5 | request.path 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/postcode_sanitizer.rb: -------------------------------------------------------------------------------- 1 | class PostcodeSanitizer 2 | def self.sanitize(postcode) 3 | if postcode.present? 4 | # Strip trailing whitespace, non-alphanumerics, and use the 5 | # uk_postcode gem to potentially transpose O/0 and I/1. 6 | UKPostcode.parse(postcode.gsub(/[^a-z0-9 ]/i, "").strip).to_s 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/sanitiser/strategy.rb: -------------------------------------------------------------------------------- 1 | module Sanitiser 2 | class Strategy 3 | class SanitisingError < StandardError; end 4 | 5 | class << self 6 | def call(input, sanitize_null_bytes: false) 7 | input 8 | .force_encoding(Encoding::ASCII_8BIT) 9 | .encode!(Encoding::UTF_8) 10 | if sanitize_null_bytes && Rack::UTF8Sanitizer::NULL_BYTE_REGEX.match?(input) 11 | raise NullByteInString 12 | end 13 | rescue StandardError 14 | raise SanitisingError, "Non-UTF-8 (or null) character in the query, cookie or form data" 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/simple_smart_answers/base_error.rb: -------------------------------------------------------------------------------- 1 | module SimpleSmartAnswers 2 | class BaseError < StandardError; end 3 | end 4 | -------------------------------------------------------------------------------- /lib/simple_smart_answers/flow.rb: -------------------------------------------------------------------------------- 1 | require "simple_smart_answers/node" 2 | require "simple_smart_answers/state" 3 | 4 | module SimpleSmartAnswers 5 | class Flow 6 | def initialize(node_details) 7 | @nodes = {} 8 | node_details.each_with_index do |n, i| 9 | node = Node.new(self, n) 10 | @nodes[node.slug] = node 11 | @start_node = node if i.zero? 12 | end 13 | end 14 | 15 | attr_reader :start_node 16 | 17 | def node_for_slug(slug) 18 | @nodes[slug] 19 | end 20 | 21 | def state_for_responses(responses) 22 | state = State.new(self, start_node) 23 | state.process_responses(responses) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/simple_smart_answers/invalid_response.rb: -------------------------------------------------------------------------------- 1 | module SimpleSmartAnswers 2 | class InvalidResponse < BaseError; end 3 | end 4 | -------------------------------------------------------------------------------- /lib/simple_smart_answers/state.rb: -------------------------------------------------------------------------------- 1 | module SimpleSmartAnswers 2 | class State 3 | def initialize(flow, initial_node) 4 | @flow = flow 5 | @current_node = initial_node 6 | @completed_questions = [] 7 | @error = false 8 | end 9 | 10 | attr_reader :current_node, :completed_questions 11 | 12 | def error? 13 | @error 14 | end 15 | 16 | def process_responses(responses) 17 | responses.each do |response| 18 | add_response(response) 19 | break if error? 20 | end 21 | self 22 | end 23 | 24 | def add_response(response_slug) 25 | response = @current_node.process_response(response_slug) 26 | @current_node = response.next_node 27 | @completed_questions << response 28 | rescue InvalidResponse 29 | @error = true 30 | end 31 | 32 | def current_question_number 33 | @completed_questions.size + 1 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/jasmine.rake: -------------------------------------------------------------------------------- 1 | desc "Run Javascript tests" 2 | task jasmine: [:environment] do 3 | sh "yarn run jasmine:ci" 4 | end 5 | -------------------------------------------------------------------------------- /lib/tasks/lint.rake: -------------------------------------------------------------------------------- 1 | desc "Run all linters" 2 | task lint: :environment do 3 | sh "bundle exec rubocop" 4 | sh "bundle exec erb_lint --lint-all" 5 | sh "yarn run lint" 6 | end 7 | -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/log/.gitkeep -------------------------------------------------------------------------------- /spec/factories/content_items.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :content_item do 3 | initialize_with { new(attributes.deep_stringify_keys) } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/landing_page_statistics_data/data_four.csv: -------------------------------------------------------------------------------- 1 | ,Generation X,Millenials 2 | 2020,10,15 3 | 2021,20,25 4 | 2023,25,30 5 | 2024,30,35 6 | -------------------------------------------------------------------------------- /spec/fixtures/landing_page_statistics_data/data_one.csv: -------------------------------------------------------------------------------- 1 | ,Percentage of people being kinder to one and other 2 | 1/6/2013,0.500 3 | 1/6/2014,0.600 4 | 1/6/2015,0.700 5 | 1/6/2016,0.500 6 | 1/6/2017,0.600 7 | 1/6/2018,0.700 8 | 1/6/2019,0.550 9 | -------------------------------------------------------------------------------- /spec/fixtures/landing_page_statistics_data/data_three.csv: -------------------------------------------------------------------------------- 1 | ,The number of people working smarter not harder 2 | 31/12/2022,45775 3 | 31/3/2023,47518 4 | 30/6/2023,50546 5 | 30/9/2023,56042 6 | 31/12/2023,45768 7 | 31/3/2024,34530 8 | 30/6/2024,29585 -------------------------------------------------------------------------------- /spec/fixtures/landing_page_statistics_data/data_two.csv: -------------------------------------------------------------------------------- 1 | ,Percentage of people being kinder to one and other 2 | 1/6/2013,0.500 3 | 1/6/2014,0.600 4 | 1/6/2015,0.700 5 | 1/6/2016,0.500 6 | 1/6/2017,0.600 7 | 1/6/2018,0.700 8 | 1/6/2019,0.500 9 | 1/6/2020, 10 | 1/6/2021, 11 | 1/6/2022,0.500 12 | 1/6/2023,0.600 -------------------------------------------------------------------------------- /spec/fixtures/local-content-items/my-json-item.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_name": "json_page" 3 | } -------------------------------------------------------------------------------- /spec/fixtures/local-content-items/my-yaml-item.yml: -------------------------------------------------------------------------------- 1 | schema_name: yaml_page 2 | -------------------------------------------------------------------------------- /spec/fixtures/single-calendar.json: -------------------------------------------------------------------------------- 1 | { 2 | "need_id": 42, 3 | "divisions": { 4 | "england-and-wales": { 5 | "2011": [ 6 | { 7 | "title": "New Year's Day", 8 | "date": "02/01/2011", 9 | "notes": "Substitute day" 10 | } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /spec/helpers/block_helper_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe BlockHelper do 2 | include described_class 3 | 4 | describe "#render_block" do 5 | it "returns an empty string when a partial template doesn't exist" do 6 | block = instance_double(LandingPage::Block::Base, type: "not_a_block") 7 | expect(render_block(block)).to be_empty 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/helpers/currency_helper_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe CurrencyHelper do 2 | include described_class 3 | 4 | let(:sample_number) { "12000" } 5 | 6 | describe "#format_amount" do 7 | it "returns a formatted number" do 8 | expect(format_amount(sample_number)).to eq("12,000 euros") 9 | end 10 | 11 | it "returns nil if passed nil" do 12 | expect(format_amount(nil)).to be_nil 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/error_items_helper_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ErrorItemsHelper do 2 | include described_class 3 | 4 | before do 5 | flash[:validation] = [ 6 | { field: "full_name", text: "Enter full name" }, 7 | { field: "job_title", text: "Enter job title" }, 8 | ] 9 | end 10 | 11 | describe "#error_items" do 12 | it "contains error items" do 13 | expect(error_items("job_title")).to eq("Enter job title") 14 | expect(error_items("email_address")).to be_nil 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/helpers/phone_number_helper_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe PhoneNumberHelper do 2 | include described_class 3 | 4 | let(:sample_phone_text) { "023 4567 8910 (with some text)" } 5 | 6 | describe "#phone_digits" do 7 | it "returns the phone number" do 8 | expect(phone_digits(sample_phone_text)).to eq("023 4567 8910") 9 | end 10 | end 11 | 12 | describe "#phone_text" do 13 | it "returns the text after the phone number" do 14 | expect(phone_text(sample_phone_text)).to eq("(with some text)") 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/helpers/theme_type_helper_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ThemeTypeHelper do 2 | include described_class 3 | 4 | describe "no theme type" do 5 | it "returns theme type default when style is empty" do 6 | expect(style("")).to eq("theme-default") 7 | end 8 | 9 | it "returns theme type default when style is invalid" do 10 | expect(style("asdfghjkl")).to eq("theme-default") 11 | end 12 | end 13 | 14 | describe "theme 2" do 15 | it "returns theme type style 2" do 16 | expect(style(2)).to eq("theme-2") 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/javascripts/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alphagov/frontend/03515bf4d5715110e9adf1f6eb14e285bd6b2237/spec/javascripts/helpers/.gitkeep -------------------------------------------------------------------------------- /spec/javascripts/helpers/nyc-test-coverage-helper.mjs: -------------------------------------------------------------------------------- 1 | // This file runs in the browser. 2 | 3 | // Ensure window.__coverage__ is always present. 4 | window.__coverage__ ??= {}; 5 | 6 | class NycTestCoverageReporter { 7 | // The following method runs after every Jasmine describe block. 8 | static suiteDone(result) { 9 | // Transfer test coverage information from the browser to node.js. 10 | jasmine.getEnv().setSuiteProperty("__coverage__", window.__coverage__); 11 | } 12 | } 13 | 14 | jasmine.getEnv().addReporter(NycTestCoverageReporter); 15 | -------------------------------------------------------------------------------- /spec/javascripts/reporters/nyc-test-coverage-reporter.mjs: -------------------------------------------------------------------------------- 1 | // This file runs in node.js. 2 | 3 | import fsPromises from 'node:fs/promises'; 4 | import {URL} from 'node:url'; 5 | import path from 'node:path'; 6 | 7 | export default class NycTestCoverageReporter { 8 | // The following method runs after every Jasmine describe block. 9 | suiteDone({properties}) { 10 | this.coverage = properties?.__coverage__ || this.coverage; 11 | } 12 | 13 | jasmineDone() { 14 | const rootDirname = path.dirname( 15 | new URL(import.meta.resolve('#root/package.json')).pathname 16 | ); 17 | const nycCoverageFile = path.join(rootDirname, 'tmp', 'nyc_output', 'out.json'); 18 | return fsPromises.writeFile(nycCoverageFile, JSON.stringify(this.coverage)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/javascripts/unit/support.spec.js: -------------------------------------------------------------------------------- 1 | describe('Supporting code', function () { 2 | 'use strict' 3 | 4 | describe('browser history method', function () { 5 | it('returns method replacing history state', function () { 6 | expect(window.GOVUK.support.history()).toBe(window.history.replaceState) 7 | }) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /spec/models/detailed_guide_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe DetailedGuide do 2 | let(:content_store_response) { GovukSchemas::Example.find("detailed_guide", example_name: "detailed_guide") } 3 | 4 | it_behaves_like "it can have single page notifications", "detailed_guide", "detailed_guide" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/field_of_operation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe FieldOfOperation do 2 | describe "#fatality_notices" do 3 | subject(:content_item) { described_class.new(content_store_response) } 4 | 5 | let(:content_store_response) do 6 | GovukSchemas::Example.find("field_of_operation", example_name: "field_of_operation") 7 | end 8 | 9 | it "returns the expected response" do 10 | expect(content_item.fatality_notices.first.title).to eq(content_store_response.dig("links", "fatality_notices", 0, "title")) 11 | expect(content_item.fatality_notices.first.base_path).to eq(content_store_response.dig("links", "fatality_notices", 0, "base_path")) 12 | expect(content_item.fatality_notices.first.to_h["details"]).to eq(content_store_response.dig("links", "fatality_notices", 0, "details")) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/models/fields_of_operation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe FieldsOfOperation do 2 | describe "#fields_of_operation" do 3 | subject(:content_item) { described_class.new(content_store_response) } 4 | 5 | let(:content_store_response) do 6 | GovukSchemas::Example.find("fields_of_operation", example_name: "fields_of_operation") 7 | end 8 | 9 | it "returns the expected response" do 10 | expect(content_item.fields_of_operation.first.title).to eq(content_store_response.dig("links", "fields_of_operation", 0, "title")) 11 | expect(content_item.fields_of_operation.first.base_path).to eq(content_store_response.dig("links", "fields_of_operation", 0, "base_path")) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/models/flexible_page/flexible_section/page_title_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe FlexiblePage::FlexibleSection::PageTitle do 2 | subject(:page_title) do 3 | described_class.new({ 4 | "context" => "My Context", 5 | "heading_text" => "My Heading", 6 | "lead_paragraph" => "Welcome to this page", 7 | }, FlexiblePage.new({})) 8 | end 9 | 10 | describe "#initialize" do 11 | it "sets the attributes from the contents hash" do 12 | expect(page_title.context).to eq("My Context") 13 | expect(page_title.heading_text).to eq("My Heading") 14 | expect(page_title.lead_paragraph).to eq("Welcome to this page") 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/models/flexible_page/flexible_section_factory_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe FlexiblePage::FlexibleSectionFactory do 2 | describe ".build" do 3 | it "builds sections of the correct type" do 4 | expect(described_class.build({ "type" => "base" }, FlexiblePage.new({}))).to be_instance_of(FlexiblePage::FlexibleSection::Base) 5 | end 6 | 7 | it "raises an error if the section type is not recognised" do 8 | expect { described_class.build({ "type" => "broken" }, FlexiblePage.new({})) }.to raise_error(StandardError, "Couldn't identify a model class for type: broken") 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/models/homepage_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Homepage do 2 | let!(:content_store_response) { GovukSchemas::Example.find("homepage", example_name: "homepage_with_popular_links_on_govuk") } 3 | let(:homepage) { described_class.new(content_store_response) } 4 | 5 | describe "#popular_links" do 6 | context "when popular links in the content item" do 7 | it "returns popular links from the content item" do 8 | expected_popular_links = content_store_response 9 | .dig("links", "popular_links", 0, "details", "link_items") 10 | .collect(&:with_indifferent_access) 11 | 12 | expect(homepage.popular_links).to eq(expected_popular_links) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/action_link_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::ActionLink do 2 | it_behaves_like "it is a landing-page block" 3 | end 4 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/base_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::Base do 2 | describe "#full_width?" do 3 | it "is false by default" do 4 | expect(described_class.new({}, build(:landing_page)).full_width?).to be(false) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/block_error_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::BlockError do 2 | subject(:block_error) { described_class.new(blocks_hash, build(:landing_page)) } 3 | 4 | let(:blocks_hash) do 5 | { 6 | "type" => "block_error", 7 | "error" => StandardError.new("This error"), 8 | } 9 | end 10 | 11 | it_behaves_like "it is a landing-page block" 12 | 13 | it "has an error" do 14 | expect(block_error.error).to be_instance_of(StandardError) 15 | expect(block_error.error.message).to eq("This error") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/blocks_container_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::BlocksContainer do 2 | subject(:blocks_container) { described_class.new(blocks_hash, build(:landing_page)) } 3 | 4 | let(:blocks_hash) do 5 | { 6 | "type" => "blocks_container", 7 | "blocks" => [ 8 | { 9 | "type" => "govspeak", 10 | "content" => "

Some content!

", 11 | }, 12 | { 13 | "type" => "heading", 14 | "content" => "Porem ipsum dolor", 15 | }, 16 | { 17 | "type" => "govspeak", 18 | "content" => "

Some more content!

", 19 | }, 20 | ], 21 | } 22 | end 23 | 24 | it_behaves_like "it is a landing-page block" 25 | 26 | it "has children blocks" do 27 | expect(blocks_container.children.size).to eq(3) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/columns_layout_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::ColumnsLayout do 2 | subject(:columns_layout) { described_class.new(blocks_hash, build(:landing_page)) } 3 | 4 | let(:blocks_hash) do 5 | { 6 | "type" => "columns_layout", 7 | "blocks" => [ 8 | { 9 | "type" => "govspeak", 10 | "content" => "

Some content!

", 11 | }, 12 | { 13 | "type" => "govspeak", 14 | "content" => "

Some more content!

", 15 | }, 16 | { 17 | "type" => "govspeak", 18 | "content" => "

Even more content!

", 19 | }, 20 | ], 21 | } 22 | end 23 | 24 | it_behaves_like "it is a landing-page block" 25 | 26 | it "has column blocks" do 27 | expect(columns_layout.blocks.size).to eq(3) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/govspeak_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::Govspeak do 2 | it_behaves_like "it is a landing-page block" 3 | end 4 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/heading_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::Heading do 2 | it_behaves_like "it is a landing-page block" 3 | end 4 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/layout_base_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::LayoutBase do 2 | describe "#blocks" do 3 | subject(:layout_base) { described_class.new(blocks_hash, build(:landing_page)) } 4 | 5 | let(:blocks_hash) do 6 | { 7 | "blocks" => [ 8 | { "type" => "govspeak", "content" => "test1" }, 9 | { "type" => "govspeak", "content" => "test2" }, 10 | { "type" => "govspeak", "content" => "test3" }, 11 | ], 12 | } 13 | end 14 | 15 | it "builds all of the blocks" do 16 | expect(layout_base.blocks.count).to eq 3 17 | end 18 | 19 | it "builds blocks of the correct type" do 20 | expect(layout_base.blocks.first.type).to eq("govspeak") 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/logo_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::Logo do 2 | it_behaves_like "it is a landing-page block" 3 | end 4 | -------------------------------------------------------------------------------- /spec/models/landing_page/block/quote_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::Block::Quote do 2 | it_behaves_like "it is a landing-page block" 3 | end 4 | -------------------------------------------------------------------------------- /spec/models/landing_page/block_factory_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LandingPage::BlockFactory do 2 | describe ".build" do 3 | it "builds blocks of the correct type" do 4 | expect(described_class.build({ "type" => "govspeak" }, build(:landing_page)).type).to eq("govspeak") 5 | end 6 | end 7 | 8 | describe ".build_all" do 9 | it "builds many blocks" do 10 | result = described_class.build_all([ 11 | { "type" => "govspeak" }, 12 | { "type" => "govspeak" }, 13 | { "type" => "govspeak" }, 14 | { "type" => "govspeak" }, 15 | ], build(:landing_page)) 16 | expect(result.count).to eq(4) 17 | expect(result.map(&:type)).to eq(%w[govspeak govspeak govspeak govspeak]) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/models/licence_transaction_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe LicenceTransaction do 2 | describe "#slug" do 3 | it "returns the subject slug" do 4 | content_store_response = GovukSchemas::Example.find("specialist_document", example_name: "licence-transaction") 5 | content_store_response["base_path"] = "/foo/bar" 6 | expect(described_class.new(content_store_response).slug).to eq("bar") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/models/organisation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Organisation do 2 | let(:content_store_response) do 3 | GovukSchemas::Example.find("organisation", example_name: "organisation") 4 | end 5 | 6 | describe "#brand" do 7 | it "gets the brand" do 8 | expect(described_class.new(content_store_response).brand).not_to be_nil 9 | expect(described_class.new(content_store_response).brand).to eq(content_store_response.dig("details", "brand")) 10 | end 11 | end 12 | 13 | describe "#logo" do 14 | it "gets the logo" do 15 | expect(described_class.new(content_store_response).logo).to be_instance_of(Logo) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/models/service_manual_homepage_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ServiceManualHomepage do 2 | describe "#topics" do 3 | subject(:service_manual) { described_class.new(content_store_response) } 4 | 5 | let(:content_store_response) do 6 | GovukSchemas::Example.find("service_manual_homepage", example_name: "service_manual_homepage") 7 | end 8 | 9 | it "returns the expected response" do 10 | expect(service_manual.topics.length).to eq(4) 11 | expect(service_manual.topics.first.title).to eq(content_store_response.dig("links", "children", 0, "title")) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/models/service_manual_service_toolkit_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ServiceManualServiceToolkit do 2 | describe "#topics" do 3 | subject(:service_toolkit) { described_class.new(content_store_response) } 4 | 5 | let(:content_store_response) do 6 | GovukSchemas::Example.find("service_manual_service_toolkit", example_name: "service_manual_service_toolkit") 7 | end 8 | 9 | it "returns the expected response" do 10 | expect(service_toolkit.collections.length).to eq(2) 11 | expect(service_toolkit.collections.first["title"]).to eq(content_store_response.dig("details", "collections", 0, "title")) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/models/simple_smart_answer_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe SimpleSmartAnswer do 2 | describe "#slug" do 3 | it "returns the subject slug" do 4 | content_store_response = GovukSchemas::Example.find("simple_smart_answer", example_name: "simple-smart-answer") 5 | content_store_response["base_path"] = "/foo" 6 | expect(described_class.new(content_store_response).slug).to eq("foo") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/requests/answer_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Answer" do 2 | before do 3 | content_store_has_example_item("/gwasanaethau-ar-lein-cymraeg-cthem", schema: :answer) 4 | end 5 | 6 | describe "GET show" do 7 | context "when visting answer page" do 8 | let(:base_path) { "/gwasanaethau-ar-lein-cymraeg-cthem" } 9 | 10 | it "returns 200" do 11 | get base_path 12 | 13 | expect(response).to have_http_status(:ok) 14 | end 15 | 16 | it "renders the show template" do 17 | get base_path 18 | 19 | expect(response).to render_template("show") 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/requests/case_study_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Case Study" do 2 | before do 3 | content_store_has_example_item("/government/case-studies/get-britain-building-carlisle-park", schema: :case_study) 4 | end 5 | 6 | describe "GET show" do 7 | it "returns 200" do 8 | get "/government/case-studies/get-britain-building-carlisle-park" 9 | 10 | expect(response).to have_http_status(:ok) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/requests/content_items_controller_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ContentItemsController do 2 | before do 3 | content_store_has_example_item("/government/case-studies/get-britain-building-carlisle-park", schema: :case_study) 4 | end 5 | 6 | describe "GET path" do 7 | it "sets GOVUK-Account-Session-Flash in the Vary header" do 8 | get "/government/case-studies/get-britain-building-carlisle-park" 9 | 10 | expect(response.headers["Vary"]).to include("GOVUK-Account-Session-Flash") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/requests/content_loading_problems_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Content Loading Problems" do 2 | context "when loading a draft page without permission" do 3 | before do 4 | endpoint = content_store_endpoint(draft: false) 5 | stub_request(:get, "#{endpoint}/content/").to_return(status: 403, headers: {}) 6 | end 7 | 8 | it "responds with a 403 (matching the response from content store)" do 9 | get "/" 10 | 11 | expect(response).to have_http_status(:forbidden) 12 | end 13 | end 14 | 15 | context "when the content-store is missing" do 16 | before do 17 | stub_content_store_isnt_available 18 | end 19 | 20 | it "responds with a 503 (matching the response from content store)" do 21 | get "/foreign-travel-advice" 22 | 23 | expect(response).to have_http_status(:service_unavailable) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/requests/fatality_notice_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Fatality Notice" do 2 | let(:base_path) { "/government/fatalities/sad-news" } 3 | 4 | before do 5 | content_store_has_example_item(base_path, schema: :fatality_notice) 6 | end 7 | 8 | describe "GET show" do 9 | it "returns 200" do 10 | get base_path 11 | 12 | expect(response).to have_http_status(:ok) 13 | end 14 | 15 | it "renders the show template" do 16 | get base_path 17 | 18 | expect(response).to render_template(:show) 19 | end 20 | 21 | it "sets cache-control headers" do 22 | get base_path 23 | expect(response).to honour_content_store_ttl 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/requests/favicon_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Favicon" do 2 | it "redirects permanently to an asset with a 1 day expiry" do 3 | get "/favicon.ico" 4 | 5 | expect(response.headers["Cache-Control"]).to eq("max-age=86400, public") 6 | expect(response.status).to eq(301) 7 | expect(response.location).to match(/http:\/\/www.example.com\/assets\/frontend\/favicon-(.+).ico/) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/requests/field_of_operation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Field of operation" do 2 | describe "GET show" do 3 | let(:content_item) { GovukSchemas::Example.find("field_of_operation", example_name: "field_of_operation") } 4 | let(:base_path) { content_item.fetch("base_path") } 5 | 6 | before do 7 | stub_content_store_has_item(base_path, content_item) 8 | end 9 | 10 | it "succeeds" do 11 | get base_path 12 | 13 | expect(response).to have_http_status(:ok) 14 | end 15 | 16 | it "renders the show template" do 17 | get base_path 18 | 19 | expect(response).to render_template(:show) 20 | end 21 | 22 | it "sets cache-control headers" do 23 | get base_path 24 | 25 | expect(response).to honour_content_store_ttl 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/requests/fields_of_operation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Fields of operation" do 2 | describe "GET show" do 3 | let(:content_item) { GovukSchemas::Example.find("fields_of_operation", example_name: "fields_of_operation") } 4 | let(:base_path) { content_item.fetch("base_path") } 5 | 6 | before do 7 | stub_content_store_has_item(base_path, content_item) 8 | end 9 | 10 | it "succeeds" do 11 | get base_path 12 | 13 | expect(response).to have_http_status(:ok) 14 | end 15 | 16 | it "renders the show template" do 17 | get base_path 18 | 19 | expect(response).to render_template(:index) 20 | end 21 | 22 | it "sets cache-control headers" do 23 | get base_path 24 | 25 | expect(response).to honour_content_store_ttl 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/requests/find_local_council_spec.rb: -------------------------------------------------------------------------------- 1 | require "gds_api/test_helpers/local_links_manager" 2 | 3 | RSpec.describe "Find Local Council" do 4 | include GdsApi::TestHelpers::LocalLinksManager 5 | 6 | before { content_store_has_random_item(base_path: "/find-local-council") } 7 | 8 | it "sets correct expiry headers" do 9 | get "/find-local-council" 10 | 11 | expect(response).to honour_content_store_ttl 12 | end 13 | 14 | it "returns a 404 if the local authority can't be found" do 15 | stub_local_links_manager_does_not_have_an_authority("foo") 16 | get "/find-local-council/foo" 17 | 18 | expect(response).to have_http_status(:not_found) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/requests/flexible_page_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "FlexiblePage" do 2 | describe "GET show" do 3 | context "when visiting a Flexible page" do 4 | before do 5 | ENV["ALLOW_LOCAL_CONTENT_ITEM_OVERRIDE"] = "true" 6 | stub_const("ContentItemLoader::LOCAL_ITEMS_PATH", "spec/fixtures/local-content-items") 7 | end 8 | 9 | after do 10 | ENV["ALLOW_LOCAL_CONTENT_ITEM_OVERRIDE"] = nil 11 | end 12 | 13 | let(:base_path) { "/flexible-page" } 14 | 15 | it "returns 200" do 16 | get base_path 17 | 18 | expect(response).to have_http_status(:ok) 19 | end 20 | 21 | it "renders the show template" do 22 | get base_path 23 | 24 | expect(response).to render_template("show") 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/requests/get_involved_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Get Involved" do 2 | before do 3 | stub_content_store_has_item("/government/get-involved", GovukSchemas::Example.find("get_involved", example_name: "get_involved")) 4 | stub_request(:get, /\A#{Plek.new.find('search-api')}\/search.json/).to_return(body: { "results" => [], "total" => 83 }.to_json) 5 | end 6 | 7 | describe "GET index" do 8 | it "responds with success" do 9 | get "/government/get-involved" 10 | 11 | expect(response).to have_http_status(:ok) 12 | expect(response.body).to include("Get involved") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/requests/homepage_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Homepage" do 2 | include GovukAbTesting::RspecHelpers 3 | include ContentStoreHelpers 4 | 5 | context "when loading the homepage" do 6 | before { stub_homepage_content_item } 7 | 8 | it "responds with success" do 9 | get "/" 10 | 11 | expect(response).to have_http_status(:ok) 12 | end 13 | 14 | it "sets correct expiry headers" do 15 | get "/" 16 | 17 | expect(response).to honour_content_store_ttl 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/requests/placeholder_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Placeholder" do 2 | context "when loading the placeholder page" do 3 | it "responds with success" do 4 | stub_content_store_does_not_have_item("/government") 5 | stub_content_store_does_not_have_item("/government/placeholder") 6 | get "/government/placeholder" 7 | 8 | expect(response).to have_http_status(:ok) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/requests/roadmap_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Roadmap" do 2 | describe "GET index" do 3 | before do 4 | content_store_has_random_item(base_path: "/roadmap", schema: "special_route") 5 | end 6 | 7 | it "sets the cache expiry headers" do 8 | get "/roadmap" 9 | 10 | expect(response.headers["Cache-Control"]).to eq("max-age=#{30.minutes.to_i}, public") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/requests/service_manual_homepage_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Service Manual homepage" do 2 | describe "GET show" do 3 | let(:content_item) { GovukSchemas::Example.find("service_manual_homepage", example_name: "service_manual_homepage") } 4 | let(:base_path) { content_item.fetch("base_path") } 5 | 6 | before do 7 | stub_content_store_has_item(base_path, content_item) 8 | end 9 | 10 | it "succeeds" do 11 | get base_path 12 | 13 | expect(response).to have_http_status(:ok) 14 | end 15 | 16 | it "renders the show template" do 17 | get base_path 18 | 19 | expect(response).to render_template(:index) 20 | end 21 | 22 | it "sets cache-control headers" do 23 | get base_path 24 | 25 | expect(response).to honour_content_store_ttl 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/requests/service_toolkit_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Service toolkit page" do 2 | describe "GET show" do 3 | let(:content_item) { GovukSchemas::Example.find("service_manual_service_toolkit", example_name: "service_manual_service_toolkit") } 4 | let(:base_path) { content_item.fetch("base_path") } 5 | 6 | before do 7 | stub_content_store_has_item(base_path, content_item) 8 | end 9 | 10 | it "succeeds" do 11 | get base_path 12 | 13 | expect(response).to have_http_status(:ok) 14 | end 15 | 16 | it "renders the show template" do 17 | get base_path 18 | 19 | expect(response).to render_template(:index) 20 | end 21 | 22 | it "sets cache-control headers" do 23 | get base_path 24 | 25 | expect(response).to honour_content_store_ttl 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/requests/speech_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Speech" do 2 | before do 3 | content_store_has_example_item("/government/speeches/vehicle-regulations", schema: :speech) 4 | end 5 | 6 | describe "GET show" do 7 | context "when visiting a Speech page" do 8 | let(:base_path) { "/government/speeches/vehicle-regulations" } 9 | 10 | it "returns 200" do 11 | get base_path 12 | 13 | expect(response).to have_http_status(:ok) 14 | end 15 | 16 | it "renders the show template" do 17 | get base_path 18 | 19 | expect(response).to render_template("show") 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/requests/static_error_pages_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Static Error Pages" do 2 | context "when asked for an unrecognised error" do 3 | it "returns a 404 with no body" do 4 | get "/static-error-pages/555.html" 5 | 6 | expect(response).to have_http_status(:not_found) 7 | expect(response.body).to be_empty 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/requests/take_part_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Take Part" do 2 | before do 3 | content_store_has_example_item("/government/get-involved/take-part/tp1", schema: :take_part) 4 | end 5 | 6 | describe "GET index" do 7 | it "returns 200" do 8 | get "/government/get-involved/take-part/tp1" 9 | 10 | expect(response).to have_http_status(:ok) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/routing/calendars_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Calendars" do 2 | include ContentStoreHelpers 3 | 4 | before do 5 | stub_content_store_has_item("/something", schema_name: "calendar") 6 | end 7 | 8 | it "returns 404 for a non-existent calendar" do 9 | allow(Calendar).to receive(:find).and_raise(Calendar::CalendarNotFound) 10 | get "/something" 11 | 12 | expect(get("/something")).to route_to(controller: "calendar", action: "show_calendar", slug: "something") 13 | end 14 | 15 | it "does not route an invalid slug format, and does not try to look up the calendar" do 16 | stub_content_store_does_not_have_item("/something..etc-passwd", schema_name: "calendar") 17 | 18 | expect(Calendar).not_to receive(:find) 19 | expect(get("/something..etc-passwd")).not_to route_to(controller: "calendar") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/routing/csv_previews_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "CSV previews" do 2 | it "routes old style paths to the CsvPreviewController" do 3 | expect(get("/media/000000000000000000000000/some-file.csv/preview")).to route_to( 4 | controller: "csv_preview", 5 | action: "show", 6 | id: "000000000000000000000000", 7 | filename: "some-file.csv", 8 | ) 9 | end 10 | 11 | it "routes new style paths to the CsvPreviewController" do 12 | expect(get("/csv-preview/000000000000000000000000/some-file.csv")).to route_to( 13 | controller: "csv_preview", 14 | action: "show", 15 | id: "000000000000000000000000", 16 | filename: "some-file.csv", 17 | format: "html", 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/routing/landing_pages_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Landing Pages" do 2 | include ContentStoreHelpers 3 | 4 | before do 5 | stub_content_store_has_item("/routing/constraint/test", schema_name: "landing_page") 6 | end 7 | 8 | it "routes to the LandingPage controller" do 9 | expect(get("/routing/constraint/test")).to route_to(controller: "landing_page", action: "show", path: "routing/constraint/test") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/calendar_helpers.rb: -------------------------------------------------------------------------------- 1 | module CalendarHelpers 2 | def mock_calendar_fixtures 3 | stub_const("Calendar::REPOSITORY_PATH", "spec/fixtures") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/component_helpers.rb: -------------------------------------------------------------------------------- 1 | module ComponentHelpers 2 | def component_name 3 | raise NotImplementedError, "Override this method in your test class" 4 | end 5 | 6 | def render_component(locals, &block) 7 | if block_given? 8 | render("components/#{component_name}", locals, &block) 9 | else 10 | render "components/#{component_name}", locals 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/concerns/people.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "it can have people" do |document_type, example_name, data_source: :content_store| 2 | let(:content_item) { fetch_content_item(document_type, example_name, data_source:) } 3 | 4 | it "knows it has people" do 5 | expect(described_class.new(content_item).people.count).to eq(content_item["links"]["people"].count) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/concerns/withdrawable.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "it can be withdrawn" do |document_type, example_name, data_source: :content_store| 2 | let(:content_item) { fetch_content_item(document_type, example_name, data_source:) } 3 | 4 | it "knows it is withdrawn" do 5 | expect(described_class.new(content_item).withdrawn?).to be true 6 | end 7 | 8 | it "knows when it has been withdrawn_at" do 9 | expect(described_class.new(content_item).withdrawn_at).to eq(content_item["withdrawn_notice"]["withdrawn_at"]) 10 | end 11 | 12 | it "knows it has a withdrawn_explanation" do 13 | expect(described_class.new(content_item).withdrawn_explanation).to eq(content_item["withdrawn_notice"]["explanation"]) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/concerns/worldwide_organisations.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "it can have worldwide organisations" do |document_type, example_name, data_source: :content_store| 2 | let(:content_item) { fetch_content_item(document_type, example_name, data_source:) } 3 | 4 | it "knows it has worldwide organisations" do 5 | expect(described_class.new(content_item).worldwide_organisations.count).to eq(content_item["links"]["worldwide_organisations"].count) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/content_item_helpers.rb: -------------------------------------------------------------------------------- 1 | require "support/publishing_api_graphql_helpers" 2 | 3 | module ContentItemHelpers 4 | include PublishingApiGraphqlHelpers 5 | 6 | def fetch_content_item(document_type, example_name, data_source:) 7 | case data_source 8 | when :content_store 9 | GovukSchemas::Example.find(document_type, example_name:) 10 | when :publishing_api_graphql 11 | fetch_graphql_content_item(example_name) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/election_helpers.rb: -------------------------------------------------------------------------------- 1 | module ElectionHelpers 2 | TEST_API_URL = "https://test.example.org/api/v1".freeze 3 | TEST_API_KEY = "LetMeIn".freeze 4 | 5 | def with_electoral_api_url(&block) 6 | ClimateControl.modify ELECTIONS_API_URL: TEST_API_URL, ELECTIONS_API_KEY: TEST_API_KEY, &block 7 | end 8 | 9 | def stub_api_postcode_lookup(postcode, response: "{}", status: 200) 10 | stub_request(:get, "#{TEST_API_URL}/postcode/#{postcode}?token=#{TEST_API_KEY}") 11 | .to_return(status:, body: response) 12 | end 13 | 14 | def stub_api_address_lookup(uprn, response: "{}", status: 200) 15 | stub_request(:get, "#{TEST_API_URL}/address/#{uprn}?token=#{TEST_API_KEY}") 16 | .to_return(status:, body: response) 17 | end 18 | 19 | def api_response 20 | path = Rails.root.join("spec/fixtures/electoral-result.json") 21 | File.read(path) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/jasmine-browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "srcDir": ".", 3 | "srcFiles": [ 4 | "public/assets/frontend/test-dependencies-*.js", 5 | "public/assets/frontend/dependencies-*.js", 6 | "tmp/nyc_output/assets/**/*.js" 7 | ], 8 | "specDir": "spec/javascripts", 9 | "specFiles": [ 10 | "**/*[sS]pec.js" 11 | ], 12 | "helpers": [ 13 | "helpers/*.mjs", 14 | "vendor/*.js" 15 | ], 16 | "browser": "headlessChrome", 17 | "reporters": [ 18 | "./spec/javascripts/reporters/nyc-test-coverage-reporter.mjs" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /spec/support/landing_page_blocks.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "it is a landing-page block" do 2 | let(:block_name) { described_class.name.split("::").last.underscore } 3 | 4 | # Relies on the convention that all Level 4 headings in the documentation are block names 5 | it "has a heading in the block documentation" do 6 | documentation = File.read(Rails.root.join("docs/building_blocks_for_flexible_content.md")) 7 | 8 | expect(/^#### #{block_name.titleize}\n$/.match(documentation)).not_to be_nil 9 | end 10 | 11 | it "has a correctly named partial" do 12 | expect(File).to exist(Rails.root.join("app/views/landing_page/blocks/_#{block_name}.html.erb")) 13 | end 14 | 15 | it "derives from LandingPage::Block::Base" do 16 | expect(described_class.ancestors).to include(LandingPage::Block::Base) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/location_helpers.rb: -------------------------------------------------------------------------------- 1 | require "gds_api/test_helpers/locations_api" 2 | require "gds_api/test_helpers/local_links_manager" 3 | 4 | module LocationHelpers 5 | include GdsApi::TestHelpers::LocationsApi 6 | include GdsApi::TestHelpers::LocalLinksManager 7 | 8 | def configure_locations_api_and_local_authority(postcode, authorities, local_custodian_code, snac: "00BK", gss: "E06000064") 9 | stub_locations_api_has_location( 10 | postcode, 11 | [ 12 | { 13 | "latitude" => 51.5010096, 14 | "longitude" => -0.1415870, 15 | "local_custodian_code" => local_custodian_code, 16 | }, 17 | ], 18 | ) 19 | authorities.each do |authority| 20 | stub_local_links_manager_has_a_local_authority(authority, local_custodian_code:, snac:, gss:) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/matchers/have_bank_holiday_table_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_bank_holiday_table do |attrs = {}| 2 | failure_message { "'#{page}' has the wrong rows or has incorrectly formatted datetime attributes" } 3 | 4 | match do |page| 5 | table = page.find("caption", text: "#{attrs[:title]} #{attrs[:year]}").ancestor("table") 6 | if attrs[:rows] 7 | actual_rows = table.all("tr").map { |r| r.all("th, td").map(&:text).map(&:strip) } 8 | expect(attrs[:rows]).to eq(actual_rows) 9 | expect(table.first("time")[:datetime]).to match(/\d{4}-\d{2}-\d{2}/) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/matchers/have_button_as_link_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :have_button_as_link do |text, attrs = {}| 2 | match do |actual| 3 | actual.has_css?(process_button_attributes(attrs), text:) 4 | end 5 | end 6 | 7 | def process_button_attributes(attrs) 8 | match_assert = "" 9 | match_assert << "[rel='#{attrs[:rel]}']" if attrs[:rel] 10 | match_assert << "[href='#{attrs[:href]}']" if attrs[:href] 11 | 12 | if attrs[:data] 13 | attrs[:data].each do |key, value| 14 | match_assert << "[data-#{key}='#{value}']" 15 | end 16 | end 17 | 18 | match_assert 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/matchers/honours_content_store_ttl_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :honour_content_store_ttl do 2 | match do |actual| 3 | actual.headers["Cache-Control"] == "max-age=#{15.minutes.to_i}, public" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/publishing_api_graphql_helpers.rb: -------------------------------------------------------------------------------- 1 | require "gds_api/test_helpers/publishing_api" 2 | 3 | module PublishingApiGraphqlHelpers 4 | include GdsApi::TestHelpers::PublishingApi 5 | 6 | def graphql_has_example_item(filename) 7 | graphql_response = fetch_graphql_fixture(filename) 8 | content_item = graphql_response.dig("data", "edition") 9 | 10 | stub_publishing_api_graphql_content_item( 11 | Graphql::EditionQuery.new(content_item.fetch("base_path")).query, 12 | graphql_response, 13 | ) 14 | 15 | content_item 16 | end 17 | 18 | def fetch_graphql_content_item(fixture_filename) 19 | fetch_graphql_fixture(fixture_filename).dig("data", "edition") 20 | end 21 | 22 | private 23 | 24 | def fetch_graphql_fixture(filename) 25 | json = File.read( 26 | Rails.root.join("spec", "fixtures", "graphql", "#{filename}.json"), 27 | ) 28 | JSON.parse(json) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/schema_org_helpers.rb: -------------------------------------------------------------------------------- 1 | module SchemaOrgHelpers 2 | def find_schemas 3 | schema_sections = page.find_all("script[type='application/ld+json']", visible: false) 4 | schema_sections.map { |section| JSON.parse(section.text(:all)) } 5 | end 6 | 7 | def find_schema_of_type(schema_type) 8 | find_schemas.detect { |schema| schema["@type"] == schema_type } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/system/account_home_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "AccountHome" do 2 | include ContentStoreHelpers 3 | 4 | before { stub_homepage_content_item } 5 | 6 | describe "/account/home" do 7 | it "redirects users to One Login's Your Services page" do 8 | visit account_home_path 9 | 10 | expect(page.current_url).to eq("#{GovukPersonalisation::Urls.one_login_your_services}/") 11 | end 12 | 13 | context "when there are cookie consent and _ga url parameters" do 14 | it "preserve them through the redirect" do 15 | visit account_home_path(cookie_consent: true, _ga: "abc123") 16 | 17 | expect(page.current_url).to include("cookie_consent=true") 18 | expect(page.current_url).to include("_ga=abc123") 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/system/developer_root_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Developer Root" do 2 | context "when visiting /development" do 3 | it "includes a link to at least one example" do 4 | visit "/development" 5 | 6 | expect(page).to have_link("/", href: "/") 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/system/help_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Help" do 2 | describe "the help index page" do 3 | before do 4 | payload = { 5 | base_path: "/help", 6 | format: "special_route", 7 | title: "Help using GOV.UK", 8 | description: "", 9 | } 10 | stub_content_store_has_item("/help", payload) 11 | end 12 | 13 | it "renders the help index page correctly" do 14 | visit "/help" 15 | 16 | expect(page).to have_title("Help using GOV.UK") 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/system/sign_in_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Sign in" do 2 | describe "Sign-in help page" do 3 | before do 4 | payload = { 5 | base_path: "/sign-in", 6 | format: "special_route", 7 | title: "Sign in to a service", 8 | description: "", 9 | details: { 10 | body: "", 11 | }, 12 | } 13 | stub_content_store_has_item("/sign-in", payload) 14 | end 15 | 16 | it "renders the sign-in help page correctly" do 17 | visit "/sign-in" 18 | 19 | expect(page).to have_title("Sign in to a service") 20 | 21 | expect(page).to have_text("Search GOV.UK for a service") 22 | expect(page).to have_button("Search") 23 | end 24 | 25 | it "includes search facets as hidden elements" do 26 | visit "/sign-in" 27 | 28 | expect(page).to have_field("content_purpose_supergroup[]", type: :hidden, with: "services") 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/api_error_routing_constraint_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe ApiErrorRoutingConstraint do 2 | include ContentStoreHelpers 3 | 4 | subject(:api_error_routing_constraint) { described_class.new } 5 | 6 | let(:request) { instance_double(ActionDispatch::Request, path: "/slug", env: {}, headers: {}, params: {}) } 7 | 8 | it "returns true if there's a cached error" do 9 | stub_content_store_does_not_have_item("/slug") 10 | 11 | expect(api_error_routing_constraint.matches?(request)).to be true 12 | end 13 | 14 | it "returns false if there was no error in API calls" do 15 | stub_content_store_has_item("/slug") 16 | 17 | expect(api_error_routing_constraint.matches?(request)).to be false 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/locales_validation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe "Locales Validation" do 2 | it "validates all locale files" do 3 | checker = RailsTranslationManager::LocaleChecker.new("config/locales/*.yml") 4 | 5 | expect { checker.validate_locales }.to output("Locale files are in sync, nice job!\n").to_stdout 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/postcode_sanitizer_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe PostcodeSanitizer do 2 | describe "#sanitize" do 3 | it "strips trailing spaces from entered postcodes" do 4 | expect(described_class.sanitize("WC2B 6NH ")).to eq("WC2B 6NH") 5 | end 6 | 7 | it "strips non-alphanumerics from entered postcodes" do 8 | expect(described_class.sanitize("WC2B -6NH]")).to eq("WC2B 6NH") 9 | end 10 | 11 | it "transposes O/0 and I/1 if necessary" do 12 | expect(described_class.sanitize("WIA OAA")).to eq("W1A 0AA") 13 | end 14 | 15 | context "when the postcode parameter is nil or an empty string" do 16 | it "returns nil and does not try to perform sanitization" do 17 | expect(described_class.sanitize("")).to be_nil 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/sanitiser/strategy_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Sanitiser::Strategy do 2 | context "with sanitize_null_bytes true" do 3 | it "raises an error if the string contains a null byte" do 4 | expect { described_class.call("Hello\x00World!", sanitize_null_bytes: true) }.to raise_error(Sanitiser::Strategy::SanitisingError) 5 | end 6 | end 7 | 8 | context "with sanitize_null_bytes false" do 9 | it "does not raise an error if the string contains a null byte" do 10 | expect { described_class.call("Hello\x00World!", sanitize_null_bytes: false) }.not_to raise_error 11 | end 12 | end 13 | end 14 | --------------------------------------------------------------------------------