From 51420d371285faab63af01763397e1c1c3ade20d Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 09:52:11 +0000 Subject: [PATCH 01/24] Spit top and bottom partials into component parts 'top' and 'bottom' are not very descriptive names, and also mean we end up with a lot partials including other partials. This change make things clearer by separating out the partials, at the cost of some additional turbo frames. Move alert guidance --- app/controllers/forecasts_controller.rb | 6 +- app/views/forecasts/_alert_guidance.html.erb | 2 + app/views/forecasts/_bottom.html.erb | 6 -- app/views/forecasts/_forecast_tabs.html.erb | 14 ++-- app/views/forecasts/_predictions.html.erb | 15 ++-- app/views/forecasts/_sharing.html.erb | 74 ++++++++++---------- app/views/forecasts/_top.html.erb | 4 -- app/views/forecasts/show.html.erb | 8 ++- 8 files changed, 66 insertions(+), 63 deletions(-) delete mode 100644 app/views/forecasts/_bottom.html.erb delete mode 100644 app/views/forecasts/_top.html.erb diff --git a/app/controllers/forecasts_controller.rb b/app/controllers/forecasts_controller.rb index 32f808e6..57b90e64 100644 --- a/app/controllers/forecasts_controller.rb +++ b/app/controllers/forecasts_controller.rb @@ -16,8 +16,10 @@ def show respond_to do |format| format.turbo_stream do render turbo_stream: [ - turbo_stream.replace("forecasts-frame-top", partial: "forecasts/top"), - turbo_stream.replace("forecasts-frame-bottom", partial: "forecasts/bottom") + turbo_stream.replace("forecast-tabs-frame", partial: "forecasts/forecast_tabs"), + turbo_stream.replace("alert-guidance-frame", partial: "forecasts/alert_guidance"), + turbo_stream.replace("predictions-frame", partial: "forecasts/predictions"), + turbo_stream.replace("sharing-frame", partial: "forecasts/sharing") ] end format.html diff --git a/app/views/forecasts/_alert_guidance.html.erb b/app/views/forecasts/_alert_guidance.html.erb index 1a27995a..b97c132e 100644 --- a/app/views/forecasts/_alert_guidance.html.erb +++ b/app/views/forecasts/_alert_guidance.html.erb @@ -1,3 +1,4 @@ + <% if @day_forecast.air_quality_alert %>

@@ -8,3 +9,4 @@

<% end %> +
diff --git a/app/views/forecasts/_bottom.html.erb b/app/views/forecasts/_bottom.html.erb deleted file mode 100644 index ace953be..00000000 --- a/app/views/forecasts/_bottom.html.erb +++ /dev/null @@ -1,6 +0,0 @@ - - <%= render "alert_guidance" %> - <%= render "predictions" %> - <%= render "sharing" %> - Forecast updated at <%= @day_forecast.air_pollution.forecasted_at.strftime("%H:%M on %A %d %B %Y") %> - \ No newline at end of file diff --git a/app/views/forecasts/_forecast_tabs.html.erb b/app/views/forecasts/_forecast_tabs.html.erb index 47646e71..cee2be65 100644 --- a/app/views/forecasts/_forecast_tabs.html.erb +++ b/app/views/forecasts/_forecast_tabs.html.erb @@ -1,6 +1,8 @@ -

Air pollution for <%= @zone.name %>

-
- <%= render(DayTabComponent.new(forecast: @forecasts.first, day: 'today', active: @day == 'today')) %> - <%= render(DayTabComponent.new(forecast: @forecasts.second, day: 'tomorrow', active: @day == 'tomorrow')) %> - <%= render(DayTabComponent.new(forecast: @forecasts.third, day: 'day_after_tomorrow', active: @day == 'day_after_tomorrow')) %> -
+ +

Air pollution for <%= @zone.name %>

+
+ <%= render(DayTabComponent.new(forecast: @forecasts.first, day: 'today', active: @day == 'today')) %> + <%= render(DayTabComponent.new(forecast: @forecasts.second, day: 'tomorrow', active: @day == 'tomorrow')) %> + <%= render(DayTabComponent.new(forecast: @forecasts.third, day: 'day_after_tomorrow', active: @day == 'day_after_tomorrow')) %> +
+
\ No newline at end of file diff --git a/app/views/forecasts/_predictions.html.erb b/app/views/forecasts/_predictions.html.erb index 7bf94f13..ff29a5d5 100644 --- a/app/views/forecasts/_predictions.html.erb +++ b/app/views/forecasts/_predictions.html.erb @@ -1,6 +1,9 @@ -
-

Other environmental forecasts

- <%= render(PredictionComponent.new(prediction: @day_forecast.uv)) %> - <%= render(PredictionComponent.new(prediction: @day_forecast.pollen)) if @day_forecast.pollen.valid? %> - <%= render "temperature_prediction", prediction: @day_forecast.temperature %> -
+ +
+

Other environmental forecasts

+ <%= render(PredictionComponent.new(prediction: @day_forecast.uv)) %> + <%= render(PredictionComponent.new(prediction: @day_forecast.pollen)) if @day_forecast.pollen.valid? %> + <%= render "temperature_prediction", prediction: @day_forecast.temperature %> +
+ Forecast updated at <%= @day_forecast.air_pollution.forecasted_at.strftime("%H:%M on %A %d %B %Y") %> +
diff --git a/app/views/forecasts/_sharing.html.erb b/app/views/forecasts/_sharing.html.erb index 8b96538e..9b997241 100644 --- a/app/views/forecasts/_sharing.html.erb +++ b/app/views/forecasts/_sharing.html.erb @@ -1,37 +1,39 @@ -
- - - + diff --git a/app/views/forecasts/_top.html.erb b/app/views/forecasts/_top.html.erb deleted file mode 100644 index 2c64ccd5..00000000 --- a/app/views/forecasts/_top.html.erb +++ /dev/null @@ -1,4 +0,0 @@ - - <%= render "location_selector" %> - <%= render "forecast_tabs" %> - \ No newline at end of file diff --git a/app/views/forecasts/show.html.erb b/app/views/forecasts/show.html.erb index 9b2da83c..a7a7c850 100644 --- a/app/views/forecasts/show.html.erb +++ b/app/views/forecasts/show.html.erb @@ -6,12 +6,14 @@

The air quality forecast displays levels of air pollution, ultraviolet rays, pollen, and temperature over the next three days for your area of interest.

- <%= render "top" %> + <%= render "location_selector" %> + <%= render "forecast_tabs" %>
+ <%= render "alert_guidance" %> <%= render "map" %> - - <%= render "bottom" %> + <%= render "predictions" %> + <%= render "sharing" %>
From 607dd00923e6de53cda9c56f390338cc95b39702 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 09:52:54 +0000 Subject: [PATCH 02/24] Fix typos --- app/views/pages/about.html.erb | 2 +- app/views/pages/health_advice.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/pages/about.html.erb b/app/views/pages/about.html.erb index d4eca35c..862046b7 100644 --- a/app/views/pages/about.html.erb +++ b/app/views/pages/about.html.erb @@ -9,7 +9,7 @@

Member local authorities pay a small annual subscription to be part of the service. The maintenance and development of the core airTEXT service is funded jointly by CERC and research projects in which CERC is involved. Grants from national and local government have contributed to airTEXT service developments in the past. airTEXT works in partnership with the GLA from time to time, for example on the Mayor of London's <%= link_to('public notifications', 'https://www.london.gov.uk/press-releases/mayoral/air-quality-alerts-warn-londoners-about-pollution', target: '_blank') %> during high pollution episodes.

How are the forecasts made?

-

The air quality forecasts are produced using <%= link_to("CERC's air pollution forecasting system", 'https://www.cerc.co.uk/forecast', target: '_blank') %>. Information from weather forecasts, forecasts of pollution across Europe from the <%= link_to('CAMS', 'https://atmosphere.copernicus.eu/', target: '_blank') %> Regional Ensemble and very detailed data on about 30,000 pollution sources across London are fed into the world-leading <%= link_to('ADMS-Urban', 'https://www.cerc.co.uk/environmental-software/ADMS-Urban-model.html', target: '_blank') %> air quality model to produce forecasts of air quality at a high degree of spatial resolution (10m). These forecasts are issued twice a day at about 7am and 7pm. The forecasts are updated throughout the day using real-time monitoring data from LondonAir. We compare the forecasts with observed pollution levels to assess the accuracy of the forecasts. These performance statistics are available <%= link_to('here', './pdfs/airTEXT 2023 performance.pdf', target: '_blank') %>.

p> +

The air quality forecasts are produced using <%= link_to("CERC's air pollution forecasting system", 'https://www.cerc.co.uk/forecast', target: '_blank') %>. Information from weather forecasts, forecasts of pollution across Europe from the <%= link_to('CAMS', 'https://atmosphere.copernicus.eu/', target: '_blank') %> Regional Ensemble and very detailed data on about 30,000 pollution sources across London are fed into the world-leading <%= link_to('ADMS-Urban', 'https://www.cerc.co.uk/environmental-software/ADMS-Urban-model.html', target: '_blank') %> air quality model to produce forecasts of air quality at a high degree of spatial resolution (10m). These forecasts are issued twice a day at about 7am and 7pm. The forecasts are updated throughout the day using real-time monitoring data from LondonAir. We compare the forecasts with observed pollution levels to assess the accuracy of the forecasts. These performance statistics are available <%= link_to('here', './pdfs/airTEXT 2023 performance.pdf', target: '_blank') %>.

The hourly concentrations of four pollutants are calculated: nitrogen dioxide (NO2), particulates (PM10 and PM2.5) and ozone (O3). From the hourly concentrations the daily air quality index (<%= link_to('DAQI', 'https://uk-air.defra.gov.uk/air-pollution/daqi', target: '_blank') %>) of each pollutant is derived. The overall air quality index is determined by the highest index for any of these pollutants.

airTEXT issues an alert for a local authority or region if at least 10% of the geographical area is predicted to reach MODERATE or above.

Forecast values of UV and temperature are supplied by <%= link_to('DTN', 'https://www.dtn.com/weather/', target: '_blank') %>. The pollen forecast is supplied by the <%= link_to('Met Office', 'https://www.metoffice.gov.uk/weather/warnings-and-advice/seasonal-advice/pollen-forecast', target: '_blank') %>.

diff --git a/app/views/pages/health_advice.html.erb b/app/views/pages/health_advice.html.erb index 22da4b7e..5a6a12bf 100644 --- a/app/views/pages/health_advice.html.erb +++ b/app/views/pages/health_advice.html.erb @@ -4,7 +4,7 @@

Health advice

Air pollution index

-

The index used to describe air quality is the <%= link_to('daily air quality index (DAQI)', 'http://uk-air.defra.gov.uk/air-pollution/daqi') %>% for the UK.

+

The index used to describe air quality is the <%= link_to('daily air quality index (DAQI)', 'http://uk-air.defra.gov.uk/air-pollution/daqi') %> for the UK.

The index represents air pollution using a 1 to 10 scale divided into four bands:

From df2ad44cee75674089696a4dcf93b830c465c353 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 10:47:28 +0000 Subject: [PATCH 03/24] Fix map attribution For some reason we had multiple attributions floating around, those have now been removed. We still retain the bottom right attribution as required. We also shrink the Maptiler logo slightly to prevent it overlapping with the attribution on mobile. --- app/assets/stylesheets/leaflet.scss | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/assets/stylesheets/leaflet.scss b/app/assets/stylesheets/leaflet.scss index fa8b5815..24f30f26 100644 --- a/app/assets/stylesheets/leaflet.scss +++ b/app/assets/stylesheets/leaflet.scss @@ -20,3 +20,18 @@ /* From node_modules/leaflet.fullscreen/icon-fullscreen.svg */ background-image: url('data:image/svg+xml;charset=UTF-8,'); } + +.leaflet-pane { + .maplibregl-ctrl-attrib { + display: none; /* Hide duplicate attributions */ + } +} + +.leaflet-container { + a { + left: 2px !important; + img { + width: 50px; /* Make the Maptiler logo smaller to fit on mobile */ + } + } +} From 30a44c040cd2c45a1e02c7532f5296b51dd4815f Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 10:50:29 +0000 Subject: [PATCH 04/24] Move map controls It makes more sense to have the geolocation button next to the location search and the full screen control next to the zoom controls. --- app/javascript/controllers/map_controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/controllers/map_controller.js b/app/javascript/controllers/map_controller.js index ac362643..05461f9d 100644 --- a/app/javascript/controllers/map_controller.js +++ b/app/javascript/controllers/map_controller.js @@ -59,7 +59,7 @@ export default class MapController extends Controller { zoomControl: false, fullscreenControl: true, fullscreenControlOptions: { - position: "topright", + position: "bottomright", }, }); @@ -165,7 +165,7 @@ export default class MapController extends Controller { addGeolocationControl() { new LocateControl({ - position: "bottomright", + position: "topright", }).addTo(this.map); } From 01afc8e6a773f5a6c26027bee230b19f8de808e6 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:20:32 +0000 Subject: [PATCH 05/24] Improve formatting of geocoder control Make it work better on small screens, and match the design of the other controls Improve formatting of geocoder control --- app/assets/stylesheets/leaflet.scss | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/assets/stylesheets/leaflet.scss b/app/assets/stylesheets/leaflet.scss index 24f30f26..c7da1b25 100644 --- a/app/assets/stylesheets/leaflet.scss +++ b/app/assets/stylesheets/leaflet.scss @@ -30,8 +30,31 @@ .leaflet-container { a { left: 2px !important; + img { width: 50px; /* Make the Maptiler logo smaller to fit on mobile */ } } } + +.leaflet-control:not(.leaflet-control-attribution) { + border: 2px solid rgb(0 0 0 / 20%); + box-shadow: none; + border-radius: 4px; +} + +.leaflet-top:has(.leaflet-ctrl-geocoder) { + max-width: calc(100% - 60px); + + .leaflet-ctrl-geocoder { + max-width: 100%; + + form { + max-width: 100%; + + input { + height: 30px; + } + } + } +} From 5f5cec40ea7af7df6385c70c30ba8796917e3d2e Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:04:13 +0000 Subject: [PATCH 06/24] Improve warning icon formatting Improve warning icon formatting --- app/assets/stylesheets/application.tailwind.css.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index fefb1167..f23906fc 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -70,7 +70,7 @@ main { } .daqi-indicator { - @apply size-9 rounded-full mx-auto my-1 flex justify-center items-center; + @apply size-7 rounded-full mx-auto my-1 flex justify-center items-center p-[3px]; } .daqi-label { From cc1cd498f078059ebbd484e03c64ecf4c2de4e84 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 14:26:28 +0000 Subject: [PATCH 07/24] Fix warning icon having the wrong colour when pollution is very high --- app/components/day_tab_component.rb | 2 +- spec/components/day_tab_component_spec.rb | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/components/day_tab_component.rb b/app/components/day_tab_component.rb index 24b84f4f..05f7d13f 100644 --- a/app/components/day_tab_component.rb +++ b/app/components/day_tab_component.rb @@ -10,6 +10,6 @@ def daqi_indicator_colour_class end def icon_stroke_colour_class - (@forecast.air_pollution.label == "HIGH") ? "stroke-white" : "stroke-black" + ["High", "Very high"].include?(@forecast.air_pollution.daqi_label) ? "stroke-white" : "stroke-black" end end diff --git a/spec/components/day_tab_component_spec.rb b/spec/components/day_tab_component_spec.rb index 47f71d88..ca1828c3 100644 --- a/spec/components/day_tab_component_spec.rb +++ b/spec/components/day_tab_component_spec.rb @@ -107,20 +107,28 @@ describe "icon_stroke_colour_class" do let(:component) { - forecast = FactoryBot.build(:forecast, air_pollution: FactoryBot.build(:air_pollution_prediction, label: label)) + forecast = FactoryBot.build(:forecast, air_pollution: FactoryBot.build(:air_pollution_prediction, level)) DayTabComponent.new(forecast: forecast, day: :today) } - context "when the air pollution label is _HIGH_" do - let(:label) { "HIGH" } + context "when the air pollution label is _Very high_" do + let(:level) { :very_high } it "returns _stroke-white_" do expect(component.icon_stroke_colour_class).to eq("stroke-white") end end - context "when the air pollution label is _MODERATE_" do - let(:label) { "MODERATE" } + context "when the air pollution label is _High_" do + let(:level) { :high } + + it "returns _stroke-white_" do + expect(component.icon_stroke_colour_class).to eq("stroke-white") + end + end + + context "when the air pollution label is _Moderate_" do + let(:level) { :moderate } it "returns _stroke-black_" do expect(component.icon_stroke_colour_class).to eq("stroke-black") From 44bf2246b1b0e6a501a252b429b85913412bd8c3 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 14:18:27 +0000 Subject: [PATCH 08/24] Add Farenheit temperatures --- app/models/temperature_prediction.rb | 14 ++++++++++---- .../forecasts/_temperature_prediction.html.erb | 2 +- spec/services/cerc_forecast_service_spec.rb | 8 +++++--- spec/support/features/forecast_helper.rb | 9 ++++++--- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/models/temperature_prediction.rb b/app/models/temperature_prediction.rb index cc6a2ac9..fb589cf3 100644 --- a/app/models/temperature_prediction.rb +++ b/app/models/temperature_prediction.rb @@ -1,18 +1,24 @@ class TemperaturePrediction - attr_reader :min, :max + attr_reader :min_c, :max_c, :min_f, :max_f def initialize(min:, max:) - @min = min - @max = max + @min_c = min + @max_c = max + @min_f = farenheit(min) + @max_f = farenheit(max) end def name "Temperature" end + def farenheit(celsius) + (celsius * 9 / 5) + 32 + end + # :nocov: def inspect - "#<#{self.class.name} @min=#{min} @max=#{max}>" + "#<#{self.class.name} @min=#{min_c} @max=#{max_c}>" end # :nocov: end diff --git a/app/views/forecasts/_temperature_prediction.html.erb b/app/views/forecasts/_temperature_prediction.html.erb index d5049746..8dfc85ae 100644 --- a/app/views/forecasts/_temperature_prediction.html.erb +++ b/app/views/forecasts/_temperature_prediction.html.erb @@ -1,7 +1,7 @@
<%= prediction.name %>
-
<%= "#{prediction.min.round}°C - #{prediction.max.round}" %>°C
+
<%= prediction.min_c.round %> to <%= prediction.max_c.round %>°C / <%= prediction.min_f.round %> to <%= prediction.max_f.round %>°F
diff --git a/spec/services/cerc_forecast_service_spec.rb b/spec/services/cerc_forecast_service_spec.rb index 5a97346a..dc491124 100644 --- a/spec/services/cerc_forecast_service_spec.rb +++ b/spec/services/cerc_forecast_service_spec.rb @@ -18,7 +18,7 @@ it "caches a built forecast for each zone" do api_temperature = latest_forecasts_from_api["zones"].first["forecasts"].first["temp_max"] - cached_temperature = CercForecastService.latest_forecasts_for(zone).data.first.temperature.max + cached_temperature = CercForecastService.latest_forecasts_for(zone).data.first.temperature.max_c expect(cached_temperature).to eq(api_temperature) end end @@ -100,8 +100,10 @@ expect(forecast.pollen.value).to eq(-999) - expect(forecast.temperature.min).to eq(10.0) - expect(forecast.temperature.max).to eq(16.6) + expect(forecast.temperature.min_c).to eq(10.0) + expect(forecast.temperature.min_f).to eq(50.0) + expect(forecast.temperature.max_c).to eq(16.6) + expect(forecast.temperature.max_f).to eq(61.88) end end end diff --git a/spec/support/features/forecast_helper.rb b/spec/support/features/forecast_helper.rb index 4018f059..e03a9e39 100644 --- a/spec/support/features/forecast_helper.rb +++ b/spec/support/features/forecast_helper.rb @@ -103,11 +103,14 @@ def expect_pollen_content_for_level(level) def expect_temperature_content_for_level(level) case level when :low - expect(page).to have_content("-5°C - 4°C") + expect(page).to have_content("-5 to 4°C") + expect(page).to have_content("23 to 38°F") when :moderate - expect(page).to have_content("9°C - 16°C") + expect(page).to have_content("9 to 16°C") + expect(page).to have_content("48 to 62°F") when :high - expect(page).to have_content("27°C - 31°C") + expect(page).to have_content("27 to 31°C") + expect(page).to have_content("80 to 88°F") else raise "unexpected level #{level}" end From d9569b2518861bf2788f5f59da5b85e828d9f928 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 16:20:16 +0000 Subject: [PATCH 09/24] Treat -999 pollen values as low rather than hiding them --- app/models/concerns/daqi_properties.rb | 2 ++ app/models/pollen_prediction.rb | 4 --- app/views/forecasts/_predictions.html.erb | 2 +- spec/features/visitors/view_forecasts_spec.rb | 17 ----------- spec/models/pollen_prediction_spec.rb | 28 +++++++------------ 5 files changed, 13 insertions(+), 40 deletions(-) diff --git a/app/models/concerns/daqi_properties.rb b/app/models/concerns/daqi_properties.rb index 0134f5de..33b75865 100644 --- a/app/models/concerns/daqi_properties.rb +++ b/app/models/concerns/daqi_properties.rb @@ -12,6 +12,8 @@ def daqi_label def daqi_level case @value + when -999 + :low when 1..3 :low when 4..6 diff --git a/app/models/pollen_prediction.rb b/app/models/pollen_prediction.rb index 48358c22..6b92c1de 100644 --- a/app/models/pollen_prediction.rb +++ b/app/models/pollen_prediction.rb @@ -15,10 +15,6 @@ def guidance I18n.t("prediction.guidance.pollen.#{daqi_level}") end - def valid? - value != -999 - end - # :nocov: def inspect "#<#{self.class.name} @value=#{value}>" diff --git a/app/views/forecasts/_predictions.html.erb b/app/views/forecasts/_predictions.html.erb index ff29a5d5..a1ea4b06 100644 --- a/app/views/forecasts/_predictions.html.erb +++ b/app/views/forecasts/_predictions.html.erb @@ -2,7 +2,7 @@

Other environmental forecasts

<%= render(PredictionComponent.new(prediction: @day_forecast.uv)) %> - <%= render(PredictionComponent.new(prediction: @day_forecast.pollen)) if @day_forecast.pollen.valid? %> + <%= render(PredictionComponent.new(prediction: @day_forecast.pollen)) %> <%= render "temperature_prediction", prediction: @day_forecast.temperature %>
Forecast updated at <%= @day_forecast.air_pollution.forecasted_at.strftime("%H:%M on %A %d %B %Y") %> diff --git a/spec/features/visitors/view_forecasts_spec.rb b/spec/features/visitors/view_forecasts_spec.rb index 2a027373..56b48ed2 100644 --- a/spec/features/visitors/view_forecasts_spec.rb +++ b/spec/features/visitors/view_forecasts_spec.rb @@ -158,23 +158,6 @@ expect(page).to have_css(".pollen") end end - - context "when the pollen level is -999" do - before do - forecasts = [ - Fixtures::API.zone_forecast(day: :today).merge("pollen" => -999), - Fixtures::API.zone_forecast(day: :tomorrow), - Fixtures::API.zone_forecast(day: :day_after_tomorrow) - ] - stub_cerc_api_with(forecasts) - end - - it "does not show the pollen prediction" do - visit forecast_path - - expect(page).not_to have_css(".pollen") - end - end end end end diff --git a/spec/models/pollen_prediction_spec.rb b/spec/models/pollen_prediction_spec.rb index c7e227ea..7cb76194 100644 --- a/spec/models/pollen_prediction_spec.rb +++ b/spec/models/pollen_prediction_spec.rb @@ -1,28 +1,20 @@ RSpec.describe PollenPrediction do - describe "#valid" do + describe "#daqi_label and #guidance" do let(:prediction) { FactoryBot.build(:pollen_prediction, value: value) } + let(:value) { 1 } + + it "returns the guidance for the level" do + expect(prediction.daqi_label).to eq("Low") + expect(prediction.guidance).to eq(I18n.t("prediction.guidance.pollen.low")) + end context "when the value is -999" do let(:value) { -999 } - it "returns false" do - expect(prediction.valid?).to eq(false) - end - end - context "when the value is not -999" do - let(:value) { 1 } - it "returns true" do - expect(prediction.valid?).to eq(true) + it "returns low" do + expect(prediction.daqi_label).to eq("Low") + expect(prediction.guidance).to eq(I18n.t("prediction.guidance.pollen.low")) end end end - - describe "#guidance" do - let(:prediction) { FactoryBot.build(:pollen_prediction, value: value) } - let(:value) { 1 } - - it "returns the guidance for the level" do - expect(prediction.guidance).to eq(I18n.t("prediction.guidance.pollen.low")) - end - end end From 295516aa60b8fd6c554ba8936aaf0010ef6667d0 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 12:52:33 +0000 Subject: [PATCH 10/24] Change cursor to pointer when over tabs The tabs are clickable, so we need to indicate that to the user. --- app/assets/stylesheets/application.tailwind.css.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index f23906fc..cd0ee3df 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -57,7 +57,7 @@ main { } .tab { - @apply flex-1 px-1 py-4 text-xs text-center font-bold border-[2px]; + @apply flex-1 px-1 py-4 text-xs text-center font-bold border-[2px] cursor-pointer; border-radius: 10px 10px 0 0; From 98c78653da79e884e1adf178c8d8350f3051029b Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 12:56:35 +0000 Subject: [PATCH 11/24] Improve formatting Per design --- app/assets/stylesheets/application.tailwind.css.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index cd0ee3df..ee00bd2a 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -119,14 +119,14 @@ main { .sharing { button { - @apply flex mx-auto mt-6 px-8 py-2 rounded-md text-white justify-center; + @apply flex mx-auto mt-2 px-8 py-2 rounded-md text-white justify-center; background-color: var(--main-colour); } } .forecast-timestamp { - @apply text-sm text-center text-gray-500 mt-2 block; + @apply text-xs text-gray-500 text-center mt-2 block italic; } .learning, From 49068c9f71e94734002547697af0644fb0c72a34 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:20:24 +0000 Subject: [PATCH 12/24] Change tab border colour and thickness Per design --- app/assets/stylesheets/application.tailwind.css.scss | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index ee00bd2a..eea7faf8 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -50,19 +50,19 @@ main { } .tabs { - @apply flex flex-row items-stretch border-b-[2px] border-black; + @apply flex flex-row items-stretch border-b-[1px] border-zinc-500; border-radius: 10px 10px 0 0; gap: 0.5rem; } .tab { - @apply flex-1 px-1 py-4 text-xs text-center font-bold border-[2px] cursor-pointer; + @apply flex-1 px-1 py-4 text-xs text-center font-bold border-[1px] cursor-pointer; border-radius: 10px 10px 0 0; &.active { - @apply border-black border-solid border-b-0 -mb-[2px] bg-white; + @apply border-zinc-500 border-solid border-b-0 -mb-[1px] bg-white; } &.inactive { @@ -83,9 +83,8 @@ main { } .tab-contents { - padding: 0.5rem; - border: 2px solid black; - border-top: 0; + @apply p-2 border-[1px] border-t-0 border-zinc-500; + border-radius: 0 0 10px 10px; } From bf7170942bec4f8b620339058c7568a743bc5416 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:17:45 +0000 Subject: [PATCH 13/24] Fully colour tabs when active Per design --- app/assets/stylesheets/application.tailwind.css.scss | 4 ++-- app/components/day_tab_component.html.erb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index eea7faf8..4c942415 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -62,11 +62,11 @@ main { border-radius: 10px 10px 0 0; &.active { - @apply border-zinc-500 border-solid border-b-0 -mb-[1px] bg-white; + @apply border-zinc-500 border-solid border-b-0 -mb-[1px]; } &.inactive { - @apply border-gray-400 border-dashed border-b-0 text-gray-400; + @apply border-gray-400 border-dashed border-b-0 text-gray-400 bg-white; } .daqi-indicator { diff --git a/app/components/day_tab_component.html.erb b/app/components/day_tab_component.html.erb index b9bf28e2..d05d7878 100644 --- a/app/components/day_tab_component.html.erb +++ b/app/components/day_tab_component.html.erb @@ -1,4 +1,4 @@ -<%= tag.div class: "tab #{@day} #{@active ? 'active' : 'inactive'}", +<%= tag.div class: "tab #{@day} #{@active ? 'active' : 'inactive'} #{@forecast.air_pollution.label == 'LOW' ? 'bg-white' : daqi_indicator_colour_class}", data: { date: @forecast.date.to_s, day: @day, From f468b5e0db32ae9523826d60e4987c7fb8bcb1e8 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:19:48 +0000 Subject: [PATCH 14/24] Fix heading sizing Per design --- app/views/forecasts/_forecast_tabs.html.erb | 2 +- app/views/forecasts/show.html.erb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/forecasts/_forecast_tabs.html.erb b/app/views/forecasts/_forecast_tabs.html.erb index cee2be65..1b10cedb 100644 --- a/app/views/forecasts/_forecast_tabs.html.erb +++ b/app/views/forecasts/_forecast_tabs.html.erb @@ -1,5 +1,5 @@ -

Air pollution for <%= @zone.name %>

+

Air pollution for <%= @zone.name %>

<%= render(DayTabComponent.new(forecast: @forecasts.first, day: 'today', active: @day == 'today')) %> <%= render(DayTabComponent.new(forecast: @forecasts.second, day: 'tomorrow', active: @day == 'tomorrow')) %> diff --git a/app/views/forecasts/show.html.erb b/app/views/forecasts/show.html.erb index a7a7c850..75d7b40b 100644 --- a/app/views/forecasts/show.html.erb +++ b/app/views/forecasts/show.html.erb @@ -2,8 +2,10 @@ <% end %> -

Air quality forecast

-

The air quality forecast displays levels of air pollution, ultraviolet rays, pollen, and temperature over the next three days for your area of interest.

+
+

Air quality forecast

+

The air quality forecast displays levels of air pollution, ultraviolet rays, pollen, and temperature over the next three days for your area of interest.

+
<%= render "location_selector" %> From 07543722a6011a51e7d7a76808bfdfb462afb9b3 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:45:52 +0000 Subject: [PATCH 15/24] Fix header formatting Per design --- app/assets/stylesheets/application.tailwind.css.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index 4c942415..82e5fb66 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -18,17 +18,17 @@ header.site-header { background-color: var(--main-colour); - @apply flex flex-row p-4; + @apply flex flex-row flex-wrap p-4; .site-title { - @apply text-white flex-auto; + @apply text-white text-center; .site-name { - @apply text-2xl font-bold block; + @apply text-4xl font-bold block; } .site-strapline { - @apply text-sm block; + @apply text-xs block; } } From e8769d3f209ac1b8f4c98c6dc3b462819904dede Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 13:46:13 +0000 Subject: [PATCH 16/24] Reformat navigation Per design --- .../stylesheets/application.tailwind.css.scss | 32 ++++++++++++++++--- .../controllers/navigation_controller.js | 3 +- app/views/shared/_header.html.erb | 9 +++--- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index 82e5fb66..6b151a19 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -32,15 +32,37 @@ header.site-header { } } - nav { - @apply text-white flex-auto text-right; + button { + @apply text-5xl flex-auto text-white text-right; + + line-height: 0; + + &::before { + content: "☰"; + font-size: 4rem; + bottom: 0.25rem; + position: relative; + } - button { - @apply text-5xl; + &.menu-open { + &::before { + content: "✕"; + font-size: 3.25rem; + bottom: 0; + right: 8px; + } } + } + + nav { + @apply text-white w-full; ul { - @apply text-sm; + @apply text-xl my-4; + + li { + @apply pt-4; + } } } } diff --git a/app/javascript/controllers/navigation_controller.js b/app/javascript/controllers/navigation_controller.js index 42b5b541..161757a4 100644 --- a/app/javascript/controllers/navigation_controller.js +++ b/app/javascript/controllers/navigation_controller.js @@ -1,9 +1,10 @@ import { Controller } from "@hotwired/stimulus"; export default class NavigationController extends Controller { - static targets = ["menuList"]; + static targets = ["menuButton", "menuList"]; toggleMenu() { this.menuListTarget.classList.toggle("hidden"); + this.menuButtonTarget.classList.toggle("menu-open"); } } diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 981c2725..733593cc 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -1,12 +1,11 @@ -
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb new file mode 100644 index 00000000..0e0527e6 --- /dev/null +++ b/app/views/shared/_footer.html.erb @@ -0,0 +1,18 @@ +
+ + +
    +
  • <%= link_to "Terms & conditions", terms_and_conditions_path %>
  • +
  • <%= link_to "Privacy policy", privacy_policy_path %>
  • +
  • © CERC 2024. All rights reserved.
  • +
+
diff --git a/app/views/shared/_footer_navigation.html.erb b/app/views/shared/_footer_navigation.html.erb deleted file mode 100644 index 999af3c1..00000000 --- a/app/views/shared/_footer_navigation.html.erb +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/app/views/shared/_terms_and_conditions.html.erb b/app/views/shared/_terms_and_conditions.html.erb deleted file mode 100644 index e84fc578..00000000 --- a/app/views/shared/_terms_and_conditions.html.erb +++ /dev/null @@ -1,11 +0,0 @@ - From 4ac6aeead914b491f93aa5d5be4c82ad0519a005 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 14:47:53 +0000 Subject: [PATCH 21/24] Add active links gem This gem adds a method `active_link_to` which applies the class `active` when the link is for the current page. --- Gemfile | 1 + Gemfile.lock | 4 ++++ app/assets/stylesheets/footer.scss | 4 ++++ app/assets/stylesheets/header.scss | 4 ++++ app/views/shared/_footer.html.erb | 10 +++++----- app/views/shared/_header.html.erb | 9 +++++---- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index afbf61d3..dd0c1c45 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.3.6" +gem "active_link_to", "~> 1.0" # Active links with CSS classes gem "bootsnap", ">= 1.1.0", require: false gem "high_voltage" gem "jbuilder", "~> 2.11" diff --git a/Gemfile.lock b/Gemfile.lock index cca5ab7f..01e2c0e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -45,6 +45,9 @@ GEM erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) + active_link_to (1.0.5) + actionpack + addressable activejob (7.2.2) activesupport (= 7.2.2) globalid (>= 0.3.6) @@ -452,6 +455,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + active_link_to (~> 1.0) better_errors binding_of_caller bootsnap (>= 1.1.0) diff --git a/app/assets/stylesheets/footer.scss b/app/assets/stylesheets/footer.scss index 5e12f8b9..846e3a20 100644 --- a/app/assets/stylesheets/footer.scss +++ b/app/assets/stylesheets/footer.scss @@ -6,6 +6,10 @@ nav { li { @apply py-2; + + a.active { + @apply font-bold underline; + } } } diff --git a/app/assets/stylesheets/header.scss b/app/assets/stylesheets/header.scss index 8f204209..cf873a82 100644 --- a/app/assets/stylesheets/header.scss +++ b/app/assets/stylesheets/header.scss @@ -45,6 +45,10 @@ li { @apply pt-4; + + a.active { + @apply font-bold underline; + } } } } diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb index 0e0527e6..48121f96 100644 --- a/app/views/shared/_footer.html.erb +++ b/app/views/shared/_footer.html.erb @@ -1,11 +1,11 @@
diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 733593cc..3316b1b4 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -7,10 +7,11 @@ From 3842707f2bfd490928079e0da03624b54c92c517 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 15:44:46 +0000 Subject: [PATCH 22/24] Add how forecasts are made block Per design --- .../stylesheets/application.tailwind.css.scss | 45 +++++++++++++++++++ .../forecasts/_how_forecasts_made.html.erb | 11 +++++ app/views/forecasts/show.html.erb | 1 + app/views/shared/_footer.html.erb | 4 +- app/views/shared/_header.html.erb | 5 +-- config/routes.rb | 10 +++-- 6 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 app/views/forecasts/_how_forecasts_made.html.erb diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index 9e4d67fc..934f6ff9 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -161,4 +161,49 @@ main { @apply px-2 py-1 border border-gray-400; } } + + details { + summary { + cursor: pointer; + + &::marker { + content: none; /* Hide the default marker */ + } + + h3 { + margin: 0; + + &::after { + content: "\002B"; /* Unicode character for plus sign */ + display: flex; + width: 1.5rem; + aspect-ratio: 1; + float: right; + align-items: center; + justify-content: center; + padding-bottom: 3px; + font-size: 1.3rem; + line-height: 0; + font-weight: normal; + color: black; + border: 1px solid black; + border-radius: 100%; + } + } + } + + &[open] summary h3 { + margin-bottom: 1rem; + + &::after { + content: "\2212"; /* Unicode character for minus sign */ + } + } + } +} + +.how-forecasts-are-made { + @apply py-8 px-4; + + background-color: #eee; } diff --git a/app/views/forecasts/_how_forecasts_made.html.erb b/app/views/forecasts/_how_forecasts_made.html.erb new file mode 100644 index 00000000..c94dca3a --- /dev/null +++ b/app/views/forecasts/_how_forecasts_made.html.erb @@ -0,0 +1,11 @@ +
+
+ +

Learn how the forecasts are produced

+
+

The air quality forecasts are produced using <%= link_to("CERC's air pollution forecasting system", 'https://www.cerc.co.uk/forecast', target: '_blank') %>. Information from weather forecasts, forecasts of pollution across Europe from the <%= link_to('CAMS', 'https://atmosphere.copernicus.eu/', target: '_blank') %> Regional Ensemble and very detailed data on about 30,000 pollution sources across London are fed into the world-leading <%= link_to('ADMS-Urban', 'https://www.cerc.co.uk/environmental-software/ADMS-Urban-model.html', target: '_blank') %> air quality model to produce forecasts of air quality at a high degree of spatial resolution (10m). These forecasts are issued twice a day at about 7am and 7pm. The forecasts are updated throughout the day using real-time monitoring data from LondonAir. We compare the forecasts with observed pollution levels to assess the accuracy of the forecasts. These performance statistics are available <%= link_to('here', './pdfs/airTEXT 2023 performance.pdf', target: '_blank') %>.

+

The hourly concentrations of four pollutants are calculated: nitrogen dioxide (NO2), particulates (PM10 and PM2.5) and ozone (O3). From the hourly concentrations the daily air quality index (<%= link_to('DAQI', 'https://uk-air.defra.gov.uk/air-pollution/daqi', target: '_blank') %>) of each pollutant is derived. The overall air quality index is determined by the highest index for any of these pollutants.

+

airTEXT issues an alert for a local authority or region if at least 10% of the geographical area is predicted to reach MODERATE or above.

+

Forecast values of UV and temperature are supplied by <%= link_to('DTN', 'https://www.dtn.com/weather/', target: '_blank') %>. The pollen forecast is supplied by the <%= link_to('Met Office', 'https://www.metoffice.gov.uk/weather/warnings-and-advice/seasonal-advice/pollen-forecast', target: '_blank') %>.

+
+
diff --git a/app/views/forecasts/show.html.erb b/app/views/forecasts/show.html.erb index 75d7b40b..25a71c95 100644 --- a/app/views/forecasts/show.html.erb +++ b/app/views/forecasts/show.html.erb @@ -21,3 +21,4 @@ <%= render "subscribe" %> <%= render "learning" %> +<%= render "how_forecasts_made" %> diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb index 48121f96..76817d8d 100644 --- a/app/views/shared/_footer.html.erb +++ b/app/views/shared/_footer.html.erb @@ -2,10 +2,10 @@ diff --git a/app/views/shared/_header.html.erb b/app/views/shared/_header.html.erb index 3316b1b4..79e5ff5d 100644 --- a/app/views/shared/_header.html.erb +++ b/app/views/shared/_header.html.erb @@ -8,11 +8,10 @@ diff --git a/config/routes.rb b/config/routes.rb index e2304bde..6923cb75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,14 +3,16 @@ Rails.application.routes.draw do get "health_check" => "application#health_check" - root to: "forecasts#show" - - get "map", to: "map#show" + root to: redirect("/forecast") get :forecast, to: "forecasts#show" - + get :subscribe, to: "subscriptions#new" get :health_advice, to: "pages#health_advice" get :about, to: "pages#about" + get :contact, to: "pages#contact" + + get :privacy_policy, to: "pages#privacy_policy" + get :terms_and_conditions, to: "pages#terms_and_conditions" # If the CANONICAL_HOSTNAME env var is present, and the request doesn't come from that # hostname, redirect us to the canonical hostname with the path and query string present From a84b8a9737347c8ccecc08aadb24fe9559265a4f Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 15:43:35 +0000 Subject: [PATCH 23/24] Fix padding Per design --- app/assets/stylesheets/application.tailwind.css.scss | 12 +++++------- app/views/forecasts/_forecast_tabs.html.erb | 2 +- app/views/forecasts/show.html.erb | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/application.tailwind.css.scss b/app/assets/stylesheets/application.tailwind.css.scss index 934f6ff9..a7a77f07 100644 --- a/app/assets/stylesheets/application.tailwind.css.scss +++ b/app/assets/stylesheets/application.tailwind.css.scss @@ -17,12 +17,8 @@ --main-colour-light: #dae7f7; } -main { - padding: 1rem; -} - .tabs { - @apply flex flex-row items-stretch border-b-[1px] border-zinc-500; + @apply flex flex-row items-stretch border-b-[1px] border-zinc-500 mx-4; border-radius: 10px 10px 0 0; gap: 0.5rem; @@ -55,7 +51,7 @@ main { } .tab-contents { - @apply p-2 border-[1px] border-t-0 border-zinc-500; + @apply p-4 mx-4 border-[1px] border-t-0 border-zinc-500; border-radius: 0 0 10px 10px; } @@ -104,7 +100,7 @@ main { .subscribe { background-color: var(--main-colour-light); - @apply p-8 mt-2 text-center; + @apply p-8 mt-4 text-center; header { @apply font-bold; @@ -118,6 +114,8 @@ main { } .text-block { + @apply p-4; + h1 { @apply text-2xl font-bold mt-5 mb-3; } diff --git a/app/views/forecasts/_forecast_tabs.html.erb b/app/views/forecasts/_forecast_tabs.html.erb index 1b10cedb..bb6cadf3 100644 --- a/app/views/forecasts/_forecast_tabs.html.erb +++ b/app/views/forecasts/_forecast_tabs.html.erb @@ -1,5 +1,5 @@ -

Air pollution for <%= @zone.name %>

+

Air pollution for <%= @zone.name %>

<%= render(DayTabComponent.new(forecast: @forecasts.first, day: 'today', active: @day == 'today')) %> <%= render(DayTabComponent.new(forecast: @forecasts.second, day: 'tomorrow', active: @day == 'tomorrow')) %> diff --git a/app/views/forecasts/show.html.erb b/app/views/forecasts/show.html.erb index 25a71c95..327d2fee 100644 --- a/app/views/forecasts/show.html.erb +++ b/app/views/forecasts/show.html.erb @@ -2,7 +2,7 @@ <% end %> -
+

Air quality forecast

The air quality forecast displays levels of air pollution, ultraviolet rays, pollen, and temperature over the next three days for your area of interest.

From ac6e8f321290cf84de579c387ec897a62db06a90 Mon Sep 17 00:00:00 2001 From: "joseph@dxw.com" Date: Wed, 27 Nov 2024 16:15:13 +0000 Subject: [PATCH 24/24] Fix sharing link tests We now test the href and the visible text rather than clicking the link and checking the resulting URL. This is less flaky. --- .../features/visitors/share_forecasts_spec.rb | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/spec/features/visitors/share_forecasts_spec.rb b/spec/features/visitors/share_forecasts_spec.rb index 9d2259f6..1c920443 100644 --- a/spec/features/visitors/share_forecasts_spec.rb +++ b/spec/features/visitors/share_forecasts_spec.rb @@ -50,42 +50,50 @@ end describe "Sharing to WhatsApp" do - it "shares the forecast to WhatsApp" do - click_on "WhatsApp" - expect(page.current_url).to match(%r{https://wa.me/\?text=#{share_message}}) + it "has a WhatsApp sharing link" do + within(:xpath, "//a[contains(@href, 'https://wa.me/?text=#{share_message}')]") do + expect(page).to have_content("WhatsApp") + end end end describe "Sharing to X / Twitter" do - it "shares the forecast to Twitter" do - click_on "X / Twitter" - expect(page.current_url).to match(%r{https://x.com/intent/tweet\?text=#{share_message}}) + it "has a Twitter sharing link" do + within(:xpath, "//a[contains(@href, 'https://x.com/intent/tweet?text=#{share_message}')]") do + expect(page).to have_content("X / Twitter") + end end end describe "Sharing to Bluesky" do - it "shares the forecast to Bluesky" do - click_on "Bluesky" - expect(page.current_url).to match(%r{https://bsky.app/intent/compose\?text=#{share_message}}) + it "has a Bluesky sharing link" do + within(:xpath, "//a[contains(@href, 'https://bsky.app/intent/compose?text=#{share_message}')]") do + expect(page).to have_content("Bluesky") + end end end describe "Sharing to Facebook" do - it "shares the link to Facebook" do - click_on "Facebook" - expect(page.current_url).to match(%r{https://facebook.com/sharer/sharer.php\?u=http://www.example.com/forecast}) + it "has a Facebook sharing link" do + within(:xpath, "//a[contains(@href, 'https://facebook.com/sharer/sharer')]") do + expect(page).to have_content("Facebook") + end end end describe "Sharing via text message" do it "has an sms link" do - expect(page).to have_link(nil, href: "sms:;?&body=#{share_message}") + within(:xpath, "//a[contains(@href, 'sms:;?&body=#{share_message}')]") do + expect(page).to have_content("Text message") + end end end describe "Sharing via email" do it "has a mailto link" do - expect(page).to have_link(nil, href: "mailto:?body=#{share_message}&subject=Air%20quality%20forecast") + within(:xpath, "//a[contains(@href, 'mailto:?body=#{share_message}&subject=Air%20quality%20forecast')]") do + expect(page).to have_content("Email") + end end end end