diff --git a/server/models/views/lib/find-min-threshold.js b/server/models/views/lib/find-min-threshold.js index cf0909c36..360990474 100644 --- a/server/models/views/lib/find-min-threshold.js +++ b/server/models/views/lib/find-min-threshold.js @@ -31,7 +31,7 @@ function filterImtdThresholds (imtdThresholds) { return { alert: minObjectA ? minObjectA.value : null, - warning: minObjectW ? minObjectW.value : null + warning: minObjectW || null } } diff --git a/server/models/views/lib/latest-levels.js b/server/models/views/lib/latest-levels.js index 718d4d49b..37fac6a2a 100644 --- a/server/models/views/lib/latest-levels.js +++ b/server/models/views/lib/latest-levels.js @@ -2,6 +2,19 @@ const { formatElapsedTime } = require('../../../util') const WARNING_THRESHOLD_TYPES = ['FW RES FW', 'FW ACT FW', 'FW ACTCON FW'] +function adjustThresholdValue (value, stageDatum, subtract, postProcess) { + if (postProcess) { + if (stageDatum && stageDatum > 0) { + value -= stageDatum + } else if (stageDatum <= 0 && subtract && subtract > 0) { + value -= subtract + } else { + return parseFloat(value).toFixed(2) + } + } + return parseFloat(value).toFixed(2) +} + function getThresholdsForTargetArea (thresholds) { const filteredThresholds = thresholds.filter(threshold => threshold.status !== 'Closed' && @@ -9,9 +22,19 @@ function getThresholdsForTargetArea (thresholds) { ) const warningThresholds = findPrioritisedThresholds(filteredThresholds, WARNING_THRESHOLD_TYPES) + return warningThresholds.map(threshold => { threshold.formatted_time = formatElapsedTime(threshold.value_timestamp) threshold.isSuspendedOrOffline = threshold.status === 'Suspended' || (threshold.status === 'Active' && threshold.latest_level === null) + + // Use adjustThresholdValue for threshold_value adjustment + threshold.threshold_value = adjustThresholdValue( + threshold.threshold_value, + threshold.stage_datum, + threshold.subtract, + threshold.post_process + ) + return threshold }) } diff --git a/server/models/views/lib/process-imtd-thresholds.js b/server/models/views/lib/process-imtd-thresholds.js index 0d84f7e8f..dd8781d03 100644 --- a/server/models/views/lib/process-imtd-thresholds.js +++ b/server/models/views/lib/process-imtd-thresholds.js @@ -1,3 +1,4 @@ +const SEVERE_FLOOD_WARNING_THRESHOLD = 3 function processThreshold (threshold, stationStageDatum, stationSubtract, postProcess) { if (threshold) { if (postProcess) { @@ -14,30 +15,66 @@ function processThreshold (threshold, stationStageDatum, stationSubtract, postPr return null } -function processImtdThresholds (imtdThresholds, stationStageDatum, stationSubtract, postProcess) { +function processImtdThresholds (imtdThresholds, stationStageDatum, stationSubtract, postProcess, pc5) { const thresholds = [] - const imtdThresholdWarning = processThreshold(imtdThresholds?.warning, stationStageDatum, stationSubtract, postProcess) + const imtdThresholdWarning = calculateWarningThreshold(imtdThresholds, stationStageDatum, stationSubtract, postProcess) + const imtdThresholdAlert = calculateAlertThreshold(imtdThresholds, stationStageDatum, stationSubtract, postProcess, pc5) + if (imtdThresholdWarning) { - // Correct threshold value if value > zero (Above Ordnance Datum) [FSR-595] + thresholds.push(imtdThresholdWarning) + } + + if (imtdThresholdAlert) { + thresholds.push(imtdThresholdAlert) + } else if (pc5) { thresholds.push({ + id: 'pc5', + description: 'Top of normal range. Low lying land flooding possible above this level', + shortname: 'Top of normal range', + value: pc5 + }) + } else { return thresholds } + + return thresholds +} + +function calculateWarningThreshold (imtdThresholds, stationStageDatum, stationSubtract, postProcess) { + const imtdThresholdWarning = processThreshold(imtdThresholds?.warning?.value, stationStageDatum, stationSubtract, postProcess) + + if (imtdThresholdWarning) { + const warningType = imtdThresholds.warning.severity_value === SEVERE_FLOOD_WARNING_THRESHOLD + ? 'Severe flood warning' + : 'Flood warning' + + return { id: 'warningThreshold', - description: 'Property flooding is possible above this level. One or more flood warnings may be issued', - shortname: 'Possible flood warnings', + description: imtdThresholds.warning.severity_value + ? `${warningType} issued: ${imtdThresholds.warning.ta_name}` + : 'Property flooding is possible above this level', + shortname: imtdThresholds.warning.severity_value ? `${imtdThresholds.warning.ta_name}` : 'Possible flood warnings', value: imtdThresholdWarning - }) + } } + return null +} + +function calculateAlertThreshold (imtdThresholds, stationStageDatum, stationSubtract, postProcess, pc5) { const imtdThresholdAlert = processThreshold(imtdThresholds?.alert, stationStageDatum, stationSubtract, postProcess) + if (imtdThresholdAlert) { - thresholds.push({ + return { id: 'alertThreshold', - description: 'Low lying land flooding is possible above this level. One or more flood alerts may be issued', + description: Number(imtdThresholdAlert) !== Number(pc5) + ? 'Low lying land flooding possible above this level. One or more flood alerts may be issued' + : 'Top of normal range. Low lying land flooding possible above this level. One or more flood alerts may be issued', shortname: 'Possible flood alerts', value: imtdThresholdAlert - }) + } } - return thresholds + + return null } module.exports = processImtdThresholds diff --git a/server/models/views/station.js b/server/models/views/station.js index 904cd05da..fb9cfd5d3 100644 --- a/server/models/views/station.js +++ b/server/models/views/station.js @@ -12,9 +12,11 @@ const bannerIconId3 = 3 const outOfDateMax = 5 const dataStartDateTimeDaysToSubtract = 5 +const TOP_OF_NORMAL_RANGE = 'Top of normal range' + class ViewModel { constructor (options) { - const { station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts } = options + const { station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts, requestUrl } = options this.station = new Station(station) this.station.riverNavigation = river @@ -39,7 +41,6 @@ class ViewModel { const numSevereWarnings = warningsAlertsGroups['3'] ? warningsAlertsGroups['3'].length : 0 // Determine appropriate warning/alert text for banner - this.banner = numAlerts || numWarnings || numSevereWarnings switch (numAlerts) { @@ -170,7 +171,7 @@ class ViewModel { oneHourAgo.setHours(oneHourAgo.getHours() - 1) - // check if recent value is over one hour old0 + // check if recent value is over one hour old this.dataOverHourOld = new Date(this.recentValue.ts) < oneHourAgo this.recentValue.dateWhen = 'on ' + moment.tz(this.recentValue.ts, tz).format('D/MM/YY') @@ -230,12 +231,12 @@ class ViewModel { } this.metaDescription = `Check the latest recorded ${stationType.toLowerCase()} level and recent 5-day trend at ${stationLocation}` - // Thresholds + // Array to hold thresholds let thresholds = [] + // Check if recent value exists and add it to thresholds if (this.station.recentValue && !this.station.recentValue.err) { const tVal = this.station.type !== 'c' && this.station.recentValue._ <= 0 ? 0 : this.station.recentValue._.toFixed(2) - thresholds.push({ id: 'latest', value: tVal, @@ -243,17 +244,17 @@ class ViewModel { shortname: '' }) } + // Add the highest level threshold if available if (this.station.porMaxValue) { thresholds.push({ id: 'highest', value: this.station.porMaxValue, description: this.station.thresholdPorMaxDate - ? 'Water reaches the highest level recorded at this measuring station (recorded on ' + this.station.thresholdPorMaxDate + ')' + ? `Water reaches the highest level recorded at this measuring station (${this.station.thresholdPorMaxDate})` : 'Water reaches the highest level recorded at this measuring station', shortname: 'Highest level on record' }) } - this.imtdThresholds = imtdThresholds?.length > 0 ? filterImtdThresholds(imtdThresholds) : [] @@ -262,9 +263,9 @@ class ViewModel { this.imtdThresholds, this.station.stageDatum, this.station.subtract, - this.station.post_process + this.station.post_process, + this.station.percentile5 ) - thresholds.push(...processedImtdThresholds) if (this.station.percentile5) { @@ -273,10 +274,25 @@ class ViewModel { id: 'pc5', value: this.station.percentile5, description: 'This is the top of the normal range', - shortname: 'Top of normal range' + shortname: TOP_OF_NORMAL_RANGE }) } + // Handle chartThreshold: add tidThreshold if a valid tid is present; if not, fallback to 'pc5'; if 'pc5' is unavailable, use 'alertThreshold' with "Top of normal range" description. + // Extract tid from request URL if valid + let tid = null + try { + tid = requestUrl?.startsWith('http') ? new URL(requestUrl).searchParams.get('tid') : null + } catch (e) { + console.error('Invalid request URL:', e) + } + + // Retrieve the applicable threshold for chartThreshold + const chartThreshold = [getThresholdByThresholdId(tid, imtdThresholds, thresholds)].filter(Boolean) + + // Set chartThreshold property + this.chartThreshold = chartThreshold + // Add impacts if (impacts.length > 0) { this.station.hasImpacts = true @@ -363,7 +379,6 @@ class ViewModel { this.zoom = 14 // Forecast Data Calculations - let forecastData if (isForecast) { this.isFfoi = isForecast @@ -403,6 +418,7 @@ function stationTypeCalculator (stationTypeData) { } return stationType } + function telemetryForecastBuilder (telemetryRawData, forecastRawData, stationType) { const observed = telemetryRawData .filter(telemetry => telemetry._ !== null) // Filter out records where telemetry._ is null @@ -432,4 +448,28 @@ function telemetryForecastBuilder (telemetryRawData, forecastRawData, stationTyp } } +// Function to retrieve a threshold by tid or fall back to 'pc5' or 'alertThreshold' +const getThresholdByThresholdId = (tid, imtdThresholds, thresholds) => { + // Check if a threshold exists based on tid + const tidThreshold = tid && imtdThresholds?.find(thresh => thresh.station_threshold_id === tid) + if (tidThreshold) { + return { + id: tidThreshold.station_threshold_id, + value: Number(tidThreshold.value).toFixed(2), + description: `${tidThreshold.value}m ${tidThreshold.ta_name || ''}`, + shortname: tidThreshold.ta_name || 'Target Area Threshold' + } + } + + // Fallback to 'pc5' if present, else look for 'alertThreshold' + const pc5Threshold = thresholds.find(t => t.id === 'pc5') + if (pc5Threshold) { + return pc5Threshold + } + + // Fallback to 'alertThreshold' if description includes 'Top of normal range' + const alertThreshold = thresholds.find(t => t.id === 'alertThreshold' && t.description.includes(TOP_OF_NORMAL_RANGE)) + return alertThreshold ? { ...alertThreshold, shortname: TOP_OF_NORMAL_RANGE } : null +} + module.exports = ViewModel diff --git a/server/routes/station.js b/server/routes/station.js index 907d37d64..0162af6a5 100644 --- a/server/routes/station.js +++ b/server/routes/station.js @@ -10,6 +10,7 @@ module.exports = { handler: async (request, h) => { const { id } = request.params let { direction } = request.params + // const thresholdId = request.query.tid? // Convert human readable url to service parameter direction = direction === 'downstream' ? 'd' : 'u' @@ -54,6 +55,9 @@ module.exports = { request.server.methods.flood.getRiverStationByStationId(id, direction) ]) + // const requestUrl = request.url.href + const requestUrl = request.url.toString() + if (station.status === 'Closed') { const river = [] const model = new ViewModel({ station, telemetry, imtdThresholds, impacts, river, warningsAlerts }) @@ -67,11 +71,11 @@ module.exports = { // Forecast station const values = await request.server.methods.flood.getStationForecastData(station.wiski_id) const forecast = { forecastFlag, values } - const model = new ViewModel({ station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts }) + const model = new ViewModel({ station, telemetry, forecast, imtdThresholds, impacts, river, warningsAlerts, requestUrl }) return h.view('station', { model }) } else { // Non-forecast Station - const model = new ViewModel({ station, telemetry, imtdThresholds, impacts, river, warningsAlerts }) + const model = new ViewModel({ station, telemetry, imtdThresholds, impacts, river, warningsAlerts, requestUrl }) return h.view('station', { model }) } }, diff --git a/server/src/js/components/chart.js b/server/src/js/components/chart.js index 227a6ee4e..54e23d148 100644 --- a/server/src/js/components/chart.js +++ b/server/src/js/components/chart.js @@ -2,13 +2,13 @@ // Chart component import '../utils' +import { area as d3Area, line as d3Line, curveMonotoneX } from 'd3-shape' import { axisBottom, axisLeft } from 'd3-axis' import { scaleLinear, scaleBand, scaleTime } from 'd3-scale' import { timeFormat } from 'd3-time-format' +import { timeHour, timeMinute } from 'd3-time' import { select, selectAll, pointer } from 'd3-selection' import { max, bisector, extent } from 'd3-array' -import { timeHour, timeMinute } from 'd3-time' -import { area as d3Area, line as d3Line, curveMonotoneX } from 'd3-shape' const { forEach, simplify } = window.flood.utils @@ -776,18 +776,25 @@ function LineChart (containerId, stationId, data, options = {}) { .attr('class', 'threshold__line') .attr('aria-hidden', true) .attr('x2', xScale(xExtent[1])).attr('y2', 0) - const label = thresholdContainer.append('g') - .attr('class', 'threshold-label') + // Construct label text and split into lines of up to 35 characters + const thresholdLabel = `${threshold.level}m ${threshold.name}` + const labelSegments = thresholdLabel.match(/.{1,35}(\s|$)/g).map(line => line.trim()) + const label = thresholdContainer.append('g').attr('class', 'threshold-label') const path = label.append('path') .attr('aria-hidden', true) .attr('class', 'threshold-label__bg') const text = label.append('text') .attr('class', 'threshold-label__text') text.append('tspan').attr('font-size', 0).text('Threshold: ') - text.append('tspan').attr('x', 10).attr('y', 22).text(`${threshold.level}m ${threshold.name}`) + // Add each line of the split text (up to 35 characters per line) as separate tspans + labelSegments.forEach((line, i) => { + text.append('tspan').attr('x', 10).attr('y', (i + 1) * 22).text(line.trim()) + }) const textWidth = Math.round(text.node().getBBox().width) - path.attr('d', `m-0.5,-0.5 l${textWidth + 20},0 l0,36 l-${((textWidth + 20) / 2) - 7.5},0 l-7.5,7.5 l-7.5,-7.5 l-${((textWidth + 20) / 2) - 7.5},0 l0,-36 l0,0`) - label.attr('transform', `translate(${Math.round(width / 2 - ((textWidth + 20) / 2))}, -46)`) + const textHeight = Math.round(text.node().getBBox().height) + path.attr('d', `m-0.5,-0.5 l${textWidth + 20},0 l0,${19 + textHeight} l-${((textWidth + 20) / 2) - 7.5},0 l-7.5,7.5 l-7.5,-7.5 l-${((textWidth + 20) / 2) - 7.5},0 l0,-${19 + textHeight} l0,0`) + label.attr('transform', `translate(${Math.round(width / 2 - ((textWidth + 20) / 2))}, -${29 + textHeight})`) + const remove = thresholdContainer.append('a') .attr('role', 'button') .attr('class', 'threshold__remove') @@ -800,6 +807,7 @@ function LineChart (containerId, stationId, data, options = {}) { remove.append('circle').attr('class', 'threshold__remove-button').attr('r', 11) remove.append('line').attr('x1', -3).attr('y1', -3).attr('x2', 3).attr('y2', 3) remove.append('line').attr('y1', -3).attr('x2', -3).attr('x1', 3).attr('y2', 3) + // Set individual elements size and position thresholdContainer.attr('transform', 'translate(0,' + Math.round(yScale(threshold.level)) + ')') }) @@ -931,7 +939,7 @@ function LineChart (containerId, stationId, data, options = {}) { const showTooltip = (tooltipY = 10) => { if (!dataPoint) return // Hide threshold label - thresholdsContainer.select('.threshold--selected .threshold-label').style('visibility', 'hidden') + // thresholdsContainer.select('.threshold--selected .threshold-label').style('visibility', 'hidden') // Set tooltip text const value = dataCache.type === 'river' && (Math.round(dataPoint.value * 100) / 100) <= 0 ? '0' : dataPoint.value.toFixed(2) // *DBL below zero addition tooltipValue.text(`${value}m`) // *DBL below zero addition @@ -1109,7 +1117,8 @@ function LineChart (containerId, stationId, data, options = {}) { // const defaults = { - btnAddThresholdClass: 'defra-button-text-s' + btnAddThresholdClass: 'defra-button-text-s', + btnAddThresholdText: 'Show on chart (Visual only)' } options = Object.assign({}, defaults, options) @@ -1170,16 +1179,30 @@ function LineChart (containerId, stationId, data, options = {}) { const tooltipDescription = tooltipText.append('tspan').attr('class', 'tooltip-text').attr('x', 12).attr('dy', '1.4em') // Add optional 'Add threshold' buttons - document.querySelectorAll('[data-threshold-add]').forEach(container => { - const button = document.createElement('button') - button.className = options.btnAddThresholdClass - button.innerHTML = `Show ${container.getAttribute('data-level')}m threshold on chart (Visual only)` - button.setAttribute('aria-controls', `${containerId}-visualisation`) - button.setAttribute('data-id', container.getAttribute('data-id')) - button.setAttribute('data-threshold-add', '') - button.setAttribute('data-level', container.getAttribute('data-level')) - button.setAttribute('data-name', container.getAttribute('data-name')) - container.parentElement.replaceChild(button, container) + document.querySelectorAll('[data-threshold-add]').forEach((container, i) => { + const tooltip = document.createElement('div') + tooltip.className = 'defra-tooltip defra-tooltip--left' + tooltip.setAttribute('data-tooltip', '') + tooltip.innerHTML = ` + + @@ -35,4 +36,5 @@

Latest level{% if model.latestLevels.length > 1 %} autorefresh.updateTimeAgo() autorefresh.nextUpdate() })() - \ No newline at end of file + + diff --git a/server/views/rainfall-station.html b/server/views/rainfall-station.html index b26d530bd..12bc99879 100644 --- a/server/views/rainfall-station.html +++ b/server/views/rainfall-station.html @@ -111,7 +111,7 @@

Rainfall over the last 5 days in millimetres

- + Download data CSV (12KB)
diff --git a/server/views/river-and-sea-levels.html b/server/views/river-and-sea-levels.html index 12368fd94..da5939009 100644 --- a/server/views/river-and-sea-levels.html +++ b/server/views/river-and-sea-levels.html @@ -159,7 +159,7 @@

window.flood = {} window.flood.model = {{ model.clientModel | dump(2) | safe }} window.flood.model.mapButtonText = 'View map of levels' - window.flood.model.mpaButtonClass = 'defra-button-secondary defra-button-secondary--icon' + window.flood.model.mpaButtonClass = 'defra-button-secondary' window.flood.model.mapLayers = 'mv,ri,ti,gr,rf' window.flood.model.extent = window.flood.model.placeBox window.flood.model.data = { diff --git a/server/views/station.html b/server/views/station.html index d68dc9008..0645c2c93 100644 --- a/server/views/station.html +++ b/server/views/station.html @@ -252,7 +252,15 @@

Height

Levels that are very low or below zero are normal for some stations.

{% endif %} - Download data CSV ({% if model.isFfoi %}16{% else %}12{% endif %}KB) + + + + + + Download data CSV ({% if model.isFfoi %}16{% else %}12{% endif %}KB) + @@ -263,10 +271,12 @@

Height {% if model.thresholds.length > 0 %}
-

How levels here could affect nearby areas

- {% if model.station.hasImpacts %} +

How levels here could affect nearby areas

+
+ {% if model.station.hasImpacts %} {% endif %} +
{% for band in model.thresholds %}
diff --git a/server/views/target-area.html b/server/views/target-area.html index 82d6d0537..babc1f867 100644 --- a/server/views/target-area.html +++ b/server/views/target-area.html @@ -51,22 +51,35 @@

{{ model.pageTitle }}

+ + +

{% include "partials/sign-up-for-flood-warnings.html" %}

+

- - -
{{ model.situation | safe }}
+ + +

{{ model.areaDescription | safe }}

+ + {% if model.severity %} +

+ + Give feedback on this flood warning information + +

+ {% endif %} + {% if model.latestLevels and model.latestLevels.length > 0 and model.latestLevels.length <= 4 %} {% include "partials/latest-levels.html" %} @@ -75,22 +88,9 @@

{{ model.pageTitle }}

- Find other river and sea levels - -

- - - {% include "partials/sign-up-for-flood-warnings.html" %} - - - {% if model.severity %} -

- Could this information be better? - - Tell us how to improve it. + Find a river, sea, groundwater or rainfall level in this area

- {% endif %} {% include "partials/context-footer.html" %} @@ -111,7 +111,7 @@

{{ model.pageTitle }}

window.flood = {} window.flood.model = {{ model | dump(2) | safe }} window.flood.model.mapButtonText = {{ model.mapButtonText | dump | safe }} - window.flood.model.mapButtonClass = 'defra-button-secondary defra-button-secondary--icon govuk-!-margin-top-4' + window.flood.model.mapButtonClass = 'defra-button-secondary govuk-!-margin-top-4'; window.flood.model.mapLayers = 'mv,ts,tw,ta', window.flood.model.data = { button: 'Target Area:Map view:TA - Map view' diff --git a/test/models/floods.js b/test/data/floods.js similarity index 100% rename from test/models/floods.js rename to test/data/floods.js diff --git a/test/data/nullTelemetry.json b/test/data/nullTelemetry.json index 25a8f493d..a18c76461 100644 --- a/test/data/nullTelemetry.json +++ b/test/data/nullTelemetry.json @@ -104,5 +104,6 @@ "lon": -0.306835309255374, "lat": 50.8828385361225 }, - "warningsAlerts": [] + "warningsAlerts": [], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationAWSW.json b/test/data/stationAWSW.json index c7a43e4a5..0f774b567 100644 --- a/test/data/stationAWSW.json +++ b/test/data/stationAWSW.json @@ -124,5 +124,6 @@ "severity":"Flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationActiveAlert.json b/test/data/stationActiveAlert.json index 62d364b93..c554d0f6b 100644 --- a/test/data/stationActiveAlert.json +++ b/test/data/stationActiveAlert.json @@ -97,5 +97,6 @@ "severity_value":1, "severity":"Flood alert", "geometry":"{\"type\":\"Point\",\"coordinates\":[-0.992084272973095,51.1269040605477]}"} - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationActiveWarning.json b/test/data/stationActiveWarning.json index c127df223..15d5117f1 100644 --- a/test/data/stationActiveWarning.json +++ b/test/data/stationActiveWarning.json @@ -97,5 +97,6 @@ "severity_value":2, "severity":"Flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}"} - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationCoastal.json b/test/data/stationCoastal.json index 58a8c9efa..9bd9ba3c5 100644 --- a/test/data/stationCoastal.json +++ b/test/data/stationCoastal.json @@ -2482,5 +2482,6 @@ } ], "impacts":[], - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationForecastData.json b/test/data/stationForecastData.json index e753acc45..bdcab2153 100644 --- a/test/data/stationForecastData.json +++ b/test/data/stationForecastData.json @@ -356,5 +356,6 @@ "value": "4.3", "threshold_type": "FW ACT FW" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationGroudwater.json b/test/data/stationGroudwater.json index 17c45da03..238792002 100644 --- a/test/data/stationGroudwater.json +++ b/test/data/stationGroudwater.json @@ -83,5 +83,6 @@ "centroid":"0101000020E6100000893AA31F375496BFB3A4EA31CFAB4940", "lon":-0.0218056309757295, "lat":51.3422605891914}, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationMultipleAW.json b/test/data/stationMultipleAW.json index 4d2bf3df3..b23dfab15 100644 --- a/test/data/stationMultipleAW.json +++ b/test/data/stationMultipleAW.json @@ -163,5 +163,6 @@ "severity":"Flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationRiver.json b/test/data/stationRiver.json index 01c9fd90b..7b8a23973 100644 --- a/test/data/stationRiver.json +++ b/test/data/stationRiver.json @@ -141,5 +141,6 @@ "value": "4.3", "threshold_type": "FW RES FW" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationRiverACTCON.json b/test/data/stationRiverACTCON.json index b5abb6ffa..e212df01b 100644 --- a/test/data/stationRiverACTCON.json +++ b/test/data/stationRiverACTCON.json @@ -105,5 +105,6 @@ "value": "3.88", "threshold_type": "FW ACTCON FW" } - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationRiverSpike.json b/test/data/stationRiverSpike.json index 8ea93fc60..c4e3a1b00 100644 --- a/test/data/stationRiverSpike.json +++ b/test/data/stationRiverSpike.json @@ -2474,5 +2474,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/stationSevereWarning.json b/test/data/stationSevereWarning.json index 475ebe29a..12e4edd53 100644 --- a/test/data/stationSevereWarning.json +++ b/test/data/stationSevereWarning.json @@ -97,5 +97,6 @@ "severity_value":3, "severity":"Severe flood warning", "geometry":"{\"type\":\"Point\",\"coordinates\":[-3.03674273776571,53.8811798090409]}"} - ] + ], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/taThresholdsData.json b/test/data/taThresholdsData.json index 6297a90d0..8fa9583cb 100644 --- a/test/data/taThresholdsData.json +++ b/test/data/taThresholdsData.json @@ -2,6 +2,7 @@ "singleActiveOffline": [ { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", @@ -16,6 +17,7 @@ "singleSuspended": [ { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Pinn", "agency_name": "Moss Close", "status": "Suspended", @@ -30,6 +32,7 @@ "singleClosed": [ { "rloi_id": 7202, + "station_threshold_id": 123458, "river_name": "River Test", "agency_name": "Test Road", "status": "Closed", @@ -44,6 +47,7 @@ "multipleNormalClosed": [ { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", "status": "Active", @@ -56,6 +60,7 @@ }, { "rloi_id": 7202, + "station_threshold_id": 123458, "river_name": "River Test", "agency_name": "Test Road", "status": "Closed", @@ -70,6 +75,7 @@ "multipleNormalActiveOfflineWelshNoValues": [ { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", "status": "Active", @@ -82,6 +88,7 @@ }, { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", @@ -94,6 +101,7 @@ }, { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Welsh", "agency_name": "Welsh Station", "status": "Active", @@ -108,6 +116,7 @@ "multipleNormalSuspendedClosed": [ { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", "status": "Active", @@ -120,6 +129,7 @@ }, { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Suspended", @@ -132,6 +142,7 @@ }, { "rloi_id": 7202, + "station_threshold_id": 123458, "river_name": "River Test", "agency_name": "Test Road", "status": "Closed", @@ -146,6 +157,7 @@ "allClosedOrWelshNoValues": [ { "rloi_id": 7202, + "station_threshold_id": 123458, "river_name": "River Test", "agency_name": "Test Road", "status": "Closed", @@ -158,6 +170,7 @@ }, { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Welsh", "agency_name": "Welsh Station", "status": "Active", @@ -172,6 +185,7 @@ "singleWelshWithValues": [ { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Welsh", "agency_name": "Welsh Station", "status": "Active", @@ -186,7 +200,9 @@ "multipleLatestLevels": [ { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", + "station_threshold_id": "1747544", "agency_name": "Eastcote Road", "status": "Active", "iswales": false, @@ -198,8 +214,10 @@ }, { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", + "station_threshold_id": "1747544", "status": "Active", "iswales": false, "latest_level": "0.352", @@ -210,7 +228,9 @@ }, { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", + "station_threshold_id": "1747543", "agency_name": "Avenue Road", "status": "Active", "iswales": false, @@ -222,7 +242,9 @@ }, { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", + "station_threshold_id": "1747543", "agency_name": "Avenue Road", "status": "Active", "iswales": false, @@ -234,7 +256,9 @@ }, { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Pinn", + "station_threshold_id": "1747541", "agency_name": "Moss Close", "status": "Active", "iswales": false, @@ -246,7 +270,9 @@ }, { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Pinn", + "station_threshold_id": "1747541", "agency_name": "Moss Close", "status": "Active", "iswales": false, @@ -260,6 +286,7 @@ "overLimitLatestLevels": [ { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", "status": "Active", @@ -272,6 +299,7 @@ }, { "rloi_id": 7174, + "station_threshold_id": 123459, "river_name": "River Pinn", "agency_name": "Eastcote Road", "status": "Active", @@ -284,6 +312,7 @@ }, { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", @@ -296,6 +325,7 @@ }, { "rloi_id": 7173, + "station_threshold_id": 123465, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", @@ -308,6 +338,7 @@ }, { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Pinn", "agency_name": "Moss Close", "status": "Active", @@ -320,6 +351,7 @@ }, { "rloi_id": 7201, + "station_threshold_id": 123457, "river_name": "River Pinn", "agency_name": "Moss Close", "status": "Active", @@ -332,6 +364,7 @@ }, { "rloi_id": 1111, + "station_threshold_id": 123500, "river_name": "River Pinn", "agency_name": "Moss Close", "status": "Active", @@ -344,6 +377,7 @@ }, { "rloi_id": 1111, + "station_threshold_id": 123500, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", @@ -356,6 +390,7 @@ }, { "rloi_id": 1112, + "station_threshold_id": 123501, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", @@ -368,6 +403,7 @@ }, { "rloi_id": 1113, + "station_threshold_id": 123502, "river_name": "River Pinn", "agency_name": "Avenue Road", "status": "Active", diff --git a/test/data/toggleTipBelowZeroStation.json b/test/data/toggleTipBelowZeroStation.json index 2f3adb136..64c8deb57 100644 --- a/test/data/toggleTipBelowZeroStation.json +++ b/test/data/toggleTipBelowZeroStation.json @@ -84,5 +84,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/toggleTipRiverBedStation.json b/test/data/toggleTipRiverBedStation.json index b68a9c281..90a936d04 100644 --- a/test/data/toggleTipRiverBedStation.json +++ b/test/data/toggleTipRiverBedStation.json @@ -84,5 +84,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/toggleTipSeaLevelStation.json b/test/data/toggleTipSeaLevelStation.json index 13087fc14..93540dc71 100644 --- a/test/data/toggleTipSeaLevelStation.json +++ b/test/data/toggleTipSeaLevelStation.json @@ -84,5 +84,6 @@ "lon":-0.306835309255374, "lat":50.8828385361225 }, - "warningsAlerts":[] + "warningsAlerts":[], + "requestUrl": "http://localhost:3000/station/1001" } diff --git a/test/data/warning.json b/test/data/warning.json new file mode 100644 index 000000000..cd72105e4 --- /dev/null +++ b/test/data/warning.json @@ -0,0 +1,19 @@ +{ + "station_threshold_id":"1749487", + "station_id":"7332", + "fwis_code":"062FWF46Harpendn", + "fwis_type":"W", + "direction":"u", + "value":"2.1", + "threshold_type":"FW RES FW", + "rloi_id":7332, + "river_name":"River Lee", + "agency_name":"Harpenden", + "status":"Active", + "iswales":false, + "latest_level":"0.935", + "threshold_value":"1.6", + "value_timestamp":"2024-09-26T15:30:00.000Z", + "ta_name":"River Lee at Harpenden", + "severity_value":2 +} diff --git a/test/models/lib/latest-levels.js b/test/models/lib/latest-levels.js index 414d7b52e..517e30a11 100644 --- a/test/models/lib/latest-levels.js +++ b/test/models/lib/latest-levels.js @@ -107,43 +107,60 @@ lab.experiment('getThresholdsForTargetArea', () => { expect(result).to.have.length(0) }) - lab.test('should prioritize the first type in the WARNING_THRESHOLD_TYPES array when all thresholds are of the same type', () => { + lab.test('should adjust threshold_value using stageDatum if postProcess is true and stageDatum > 0', () => { const thresholds = [ - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T11:45:00.000Z' }, - { rloi_id: 2, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T12:45:00.000Z' } + { + rloi_id: 1, + threshold_type: 'FW RES FW', + value_timestamp: '2024-08-12T11:45:00.000Z', + threshold_value: '5.00', + stage_datum: 2.5, + subtract: 1.0, + post_process: true + } ] const result = getThresholdsForTargetArea(thresholds) - expect(result).to.have.length(2) - expect(result[0].threshold_type).to.equal('FW RES FW') - expect(result[0].formatted_time).to.equal('More than 1 hour ago') - expect(result[1].formatted_time).to.equal('0 minutes ago') + expect(result).to.have.length(1) + expect(result[0].threshold_value).to.equal('2.50') // 5.00 - 2.5 }) - lab.test('should correctly handle multiple thresholds with the same RLOI ID and type', () => { + lab.test('should adjust threshold_value using subtract if postProcess is true, stageDatum <= 0, and subtract > 0', () => { const thresholds = [ - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T11:45:00.000Z' }, - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T12:45:00.000Z' } + { + rloi_id: 2, + threshold_type: 'FW ACT FW', + value_timestamp: '2024-08-12T10:45:00.000Z', + threshold_value: '5.00', + stage_datum: 0, + subtract: 1.5, + post_process: true + } ] const result = getThresholdsForTargetArea(thresholds) expect(result).to.have.length(1) - expect(result[0].threshold_type).to.equal('FW RES FW') - expect(result[0].formatted_time).to.equal('More than 1 hour ago') + expect(result[0].threshold_value).to.equal('3.50') // 5.00 - 1.5 }) - lab.test('should ignore invalid thresholds and process only valid ones', () => { + lab.test('should not adjust threshold_value if postProcess is false', () => { const thresholds = [ - { rloi_id: 1, threshold_type: 'FW RES FW', value_timestamp: '2024-08-12T11:45:00.000Z' }, - { rloi_id: 2, threshold_type: 'INVALID TYPE', value_timestamp: '2024-08-12T12:45:00.000Z' } + { + rloi_id: 3, + threshold_type: 'FW ACTCON FW', + value_timestamp: '2024-08-12T11:45:00.000Z', + threshold_value: '4.00', + stage_datum: 1.0, + subtract: 1.5, + post_process: false + } ] const result = getThresholdsForTargetArea(thresholds) expect(result).to.have.length(1) - expect(result[0].threshold_type).to.equal('FW RES FW') - expect(result[0].formatted_time).to.equal('More than 1 hour ago') + expect(result[0].threshold_value).to.equal('4.00') // No adjustment }) }) diff --git a/test/models/lib/process-imtd-thresholds.js b/test/models/lib/process-imtd-thresholds.js index aecbb088c..0bcabb898 100644 --- a/test/models/lib/process-imtd-thresholds.js +++ b/test/models/lib/process-imtd-thresholds.js @@ -1,10 +1,11 @@ const Lab = require('@hapi/lab') const Code = require('@hapi/code') +const data = require('../../data') const lab = exports.lab = Lab.script() const processImtdThresholds = require('../../../server/models/views/lib/process-imtd-thresholds') -const alertExpectedText = { id: 'alertThreshold', description: 'Low lying land flooding is possible above this level. One or more flood alerts may be issued', shortname: 'Possible flood alerts' } -const warningExpectedText = { id: 'warningThreshold', description: 'Property flooding is possible above this level. One or more flood warnings may be issued', shortname: 'Possible flood warnings' } +const alertExpectedText = { id: 'alertThreshold', description: 'Top of normal range. Low lying land flooding possible above this level. One or more flood alerts may be issued', shortname: 'Possible flood alerts' } +const warningExpectedText = { id: 'warningThreshold', description: 'Flood warning issued: River Lee at Harpenden', shortname: 'River Lee at Harpenden' } function expectThresholds (thresholds, warningThreshold, alertThreshold) { Code.expect(thresholds.length).to.equal(2) @@ -15,11 +16,11 @@ function expectThresholds (thresholds, warningThreshold, alertThreshold) { lab.experiment('process IMTD thresholds test', () => { lab.experiment('given post processing is set to false', () => { lab.test('then thresholds should be returned as is', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, 0, 0, false) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, 0, 0, false, 1.1) expectThresholds(thresholds, '2.10', '1.10') }) lab.test('then single null thresholds should not be returned', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: null }, 0, 0, false) + const thresholds = processImtdThresholds({ alert: 1.1, warning: null }, 0, 0, false, 1.1) Code.expect(thresholds.length).to.equal(1) Code.expect(thresholds[0]).to.equal({ ...alertExpectedText, value: '1.10' }) }) @@ -34,35 +35,35 @@ lab.experiment('process IMTD thresholds test', () => { }) lab.experiment('given post processing is set to true', () => { lab.test('then thresholds should be returned as is when stageDatum is zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, 0, 0, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, 0, 0, true, 1.1) expectThresholds(thresholds, '2.10', '1.10') }) lab.test('then thresholds should be returned as adjusted when stageDatum is greater than zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, 2.2, 0, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, 2.2, 0, true, -1.1) expectThresholds(thresholds, '-0.10', '-1.10') }) lab.test('then thresholds should be returned as is when stageDatum is less than zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, -2.2, 0, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, -2.2, 0, true, 1.1) expectThresholds(thresholds, '2.10', '1.10') }) lab.test('then thresholds should be returned as adjusted when stageDatum is less than zero and stationSubtract is greater than zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, -2.2, 0.5, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, -2.2, 0.5, true, 0.6) expectThresholds(thresholds, '1.60', '0.60') }) lab.test('then thresholds should be returned as is when stageDatum is less than zero and stationSubtract is less than zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, -2.2, -0.5, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, -2.2, -0.5, true, 1.1) expectThresholds(thresholds, '2.10', '1.10') }) lab.test('then thresholds should be returned as is when stageDatum is equal to zero and stationSubtract is less than zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, 0, -0.5, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, 0, -0.5, true, 1.1) expectThresholds(thresholds, '2.10', '1.10') }) lab.test('then thresholds should be returned as adjusted when stageDatum is equal to zero and stationSubtract is greater than than zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, 0, 0.5, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, 0, 0.5, true, 0.6) expectThresholds(thresholds, '1.60', '0.60') }) lab.test('then thresholds should be returned as is when stageDatum is equal to zero and stationSubtract is zero', async () => { - const thresholds = processImtdThresholds({ alert: 1.1, warning: 2.1 }, 0, 0, true) + const thresholds = processImtdThresholds({ alert: 1.1, warning: data.warning }, 0, 0, true, 1.1) expectThresholds(thresholds, '2.10', '1.10') }) }) diff --git a/test/models/station.js b/test/models/station.js index 02ffd38ff..0d85f8888 100644 --- a/test/models/station.js +++ b/test/models/station.js @@ -37,7 +37,7 @@ lab.experiment('Station model test', () => { Code.expect(Result.thresholds[0].values).to.equal([ { id: 'warningThreshold', - description: 'Property flooding is possible above this level. One or more flood warnings may be issued', + description: 'Property flooding is possible above this level', shortname: 'Possible flood warnings', value: '3.64' } @@ -45,12 +45,12 @@ lab.experiment('Station model test', () => { Code.expect(Result.thresholds[2].values).to.equal([ { id: 'alertThreshold', - description: 'Low lying land flooding is possible above this level. One or more flood alerts may be issued', + description: 'Low lying land flooding possible above this level. One or more flood alerts may be issued', shortname: 'Possible flood alerts', value: '3.22' } ]) - Code.expect(Result.thresholds[4].values).to.equal([ + Code.expect(Result.thresholds[3].values).to.equal([ { id: 'latest', value: '0.81', @@ -64,11 +64,10 @@ lab.experiment('Station model test', () => { const viewModel = new ViewModel(stationData) const Result = viewModel - Code.expect(Result.thresholds[1].values).to.equal([ { id: 'warningThreshold', - description: 'Property flooding is possible above this level. One or more flood warnings may be issued', + description: 'Property flooding is possible above this level', shortname: 'Possible flood warnings', value: '3.22' } @@ -76,15 +75,9 @@ lab.experiment('Station model test', () => { Code.expect(Result.thresholds[2].values).to.equal([ { id: 'alertThreshold', - description: 'Low lying land flooding is possible above this level. One or more flood alerts may be issued', + description: 'Top of normal range. Low lying land flooding possible above this level. One or more flood alerts may be issued', shortname: 'Possible flood alerts', value: '2.84' - }, - { - description: 'This is the top of the normal range', - id: 'pc5', - shortname: 'Top of normal range', - value: '2.84' } ]) }) @@ -225,7 +218,7 @@ lab.experiment('Station model test', () => { Code.expect(Result.thresholds[0].values).to.equal( [ { - description: 'Property flooding is possible above this level. One or more flood warnings may be issued', + description: 'Property flooding is possible above this level', id: 'warningThreshold', shortname: 'Possible flood warnings', value: '4.20' @@ -236,7 +229,7 @@ lab.experiment('Station model test', () => { [ { id: 'alertThreshold', - description: 'Low lying land flooding is possible above this level. One or more flood alerts may be issued', + description: 'Low lying land flooding possible above this level. One or more flood alerts may be issued', shortname: 'Possible flood alerts', value: '3.88' } @@ -308,4 +301,63 @@ lab.experiment('Station model test', () => { Code.expect(Result.telemetryRefined.observed.length).to.equal(4) }) + lab.test('Test station viewModel one warning in force', async () => { + const stationData = data.stationActiveWarning + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(1) + Code.expect(Result.severityLevel).to.equal('warning') + Code.expect(Result.warningsBanner).to.equal('Flood warning for Coast from Fleetwood to Blackpool') + Code.expect(Result.warningsLink).to.equal('/target-area/012WACFB') + }) + + lab.test('Test station viewModel one alert in force', async () => { + const stationData = data.stationActiveAlert + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(1) + Code.expect(Result.severityLevel).to.equal('alert') + Code.expect(Result.alertsBanner).to.equal('There is a flood alert within 5 miles of this measuring station') + Code.expect(Result.alertsLink).to.equal('/target-area/061FAG30Alton') + }) + + lab.test('Test station viewModel one Severe Warning in force', async () => { + const stationData = data.stationSevereWarning + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(1) + Code.expect(Result.severityLevel).to.equal('severe') + Code.expect(Result.severeBanner).to.equal('Severe flood warning for Coast from Fleetwood to Blackpool') + Code.expect(Result.severeLink).to.equal('/target-area/012WACFB') + }) + + lab.test('Test station viewModel multiple Warnings and Alerts in force', async () => { + const stationData = data.stationMultipleAW + + const viewModel = new ViewModel(stationData) + + const Result = viewModel + + Code.expect(Result.station.id).to.equal(1001) + Code.expect(Result.banner).to.equal(2) + Code.expect(Result.severityLevel).to.equal('severe') + Code.expect(Result.severeBanner).to.equal('There are severe flood warnings within 5 miles of this measuring station') + Code.expect(Result.severeLink).to.equal('/alerts-and-warnings?station=1001#severe') + Code.expect(Result.alertsBanner).to.equal('There are flood alerts within 5 miles of this measuring station') + Code.expect(Result.alertsLink).to.equal('/alerts-and-warnings?station=1001#alerts') + Code.expect(Result.warningsBanner).to.equal('There are flood warnings within 5 miles of this measuring station') + Code.expect(Result.warningsLink).to.equal('/alerts-and-warnings?station=1001#warnings') + }) }) diff --git a/test/routes/station.js b/test/routes/station.js index 35b840021..3af2d32d2 100644 --- a/test/routes/station.js +++ b/test/routes/station.js @@ -388,7 +388,8 @@ lab.experiment('Test - /station/{id}', () => { Code.expect(response.payload).to.contain('Normal range 0.15m to 3.50m') Code.expect(response.payload).to.contain('Nearby levels') Code.expect(response.payload).to.contain('Upstream') - Code.expect(response.payload).to.contain('Download data CSV (12KB)') + Code.expect(response.payload).to.contain('href="/station-csv/5146"') + Code.expect(response.payload).to.contain('Download data CSV (12KB)') fullRelatedContentChecker(parse(response.payload)) validateFooterPresent(response) }) @@ -1897,7 +1898,8 @@ lab.experiment('Test - /station/{id}', () => { Code.expect(response.payload).to.contain('This data feed was interrupted') Code.expect(response.payload).to.contain('Nearby levels') Code.expect(response.payload).to.contain('Upstream') - Code.expect(response.payload).to.contain('Download data CSV (12KB)') + Code.expect(response.payload).to.contain('href="/station-csv/5146"') + Code.expect(response.payload).to.contain('Download data CSV (12KB)') }) lab.test('GET station/5146 with Normal river level does no show IMTD thresholds if not present', async () => { const floodService = require('../../server/services/flood') @@ -2162,7 +2164,22 @@ lab.experiment('Test - /station/{id}', () => { Code.expect(response.payload).to.not.contain('Normal range ') Code.expect(response.payload).to.contain('Nearby levels') Code.expect(response.payload).to.contain('Upstream') - Code.expect(response.payload).to.contain('Download data CSV (12KB)') + // This test has been simplified in other places, but keeping it strict to ensure certainty in checking the entire button structure. + const normalizeHtml = (html) => html.replace(/\s+/g, '').trim() + Code.expect( + normalizeHtml(response.payload) + ).to.contain( + normalizeHtml( + '' + + '' + + '' + + '' + + 'Download data CSV (12KB)' + + '' + ) + ) }) lab.test('GET station/1034 - Coastal River title check ', async () => { const floodService = require('../../server/services/flood') diff --git a/test/routes/target-area-2.js b/test/routes/target-area-2.js index 7dee2aa81..b217bfce9 100644 --- a/test/routes/target-area-2.js +++ b/test/routes/target-area-2.js @@ -147,7 +147,7 @@ describe('target-area route', () => { expect(response.statusCode).to.equal(200) const root = parse(response.payload) linkChecker(root.querySelectorAll('a'), - 'Find other river and sea levels', + 'Find a river, sea, groundwater or rainfall level in this area', `/river-and-sea-levels/target-area/${AREA_CODE}` ) }) @@ -175,7 +175,7 @@ describe('target-area route', () => { expect(response.statusCode).to.equal(200) const root = parse(response.payload) linkChecker(root.querySelectorAll('a'), - 'Find other river and sea levels', + 'Find a river, sea, groundwater or rainfall level in this area', `/river-and-sea-levels/target-area/${AREA_CODE}` ) }) diff --git a/test/routes/target-area.js b/test/routes/target-area.js index 77fea5b3b..54c1ee5c9 100644 --- a/test/routes/target-area.js +++ b/test/routes/target-area.js @@ -189,6 +189,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.match(/

') Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road was 0.18 metres. Property flooding is possible when it goes above 1.46 metres.') @@ -286,7 +287,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.statusCode).to.equal(200) const root = parse(response.payload) - Code.expect(response.payload).to.contain('Find other river and sea levels') + Code.expect(response.payload).to.contain('Find a river, sea, groundwater or rainfall level in this area') Code.expect(response.payload).to.contain('') const relatedContentLinks = root.querySelectorAll('.defra-related-items a') @@ -357,7 +358,7 @@ lab.experiment('Target-area tests', () => { // context footer check validateFooterPresent(response) Code.expect(response.payload).to.contain('Severe flood warning for Upper River Derwent, Stonethwaite Beck and Derwent Water') - Code.expect(response.payload).to.contain('Find other river and sea levels') + Code.expect(response.payload).to.contain('Find a river, sea, groundwater or rainfall level in this area') const anchorFound = root.querySelectorAll('a').some(a => a.attributes.href === '/river-and-sea-levels/target-area/011WAFDW' @@ -562,6 +563,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) lab.test('Displays latest level for a single suspended station', async () => { @@ -610,6 +612,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Pinn level at Moss Close is currently unavailable.

') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) lab.test('Displays multiple levels with one active but offline, one normal, and one Welsh station with no values', async () => { @@ -708,6 +711,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest levels

') Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') + Code.expect(response.payload).to.contain('

These levels will update automatically

') }) lab.test('Displays multiple levels with one Closed and one normal station', async () => { @@ -756,6 +760,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) lab.test('Displays multiple levels with one normal, one active but offline, and one Welsh station with no values', async () => { @@ -806,6 +811,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') Code.expect(response.payload).to.not.contain('

The River Welsh station level is currently unavailable.

') + Code.expect(response.payload).to.contain('

These levels will update automatically

') }) lab.test('Displays multiple levels with one normal, one suspended, and one Closed station', async () => { @@ -856,6 +862,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

The River Pinn level at Eastcote Road was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.') Code.expect(response.payload).to.contain('

The River Pinn level at Avenue Road is currently unavailable.

') Code.expect(response.payload).to.not.contain('

The River Test level at Test Road is currently unavailable.

') + Code.expect(response.payload).to.contain('

These levels will update automatically

') }) lab.test('Does not display levels if all stations are Closed or Welsh with no values', async () => { @@ -950,5 +957,7 @@ lab.experiment('Target-area tests', () => { Code.expect(response.payload).to.contain('

Latest level

') Code.expect(response.payload).to.contain('

The River Welsh level at Welsh Station was 0.35 metres. Property flooding is possible when it goes above 1.40 metres.\n \n

') + Code.expect(response.payload).to.contain('
Monitor the latest level at Welsh Station (Natural Resources Wales)') + Code.expect(response.payload).to.contain('

This level will update automatically

') }) })