Tik op een dag in de verwachtingsstrook voor een uuroverzicht van
temperatuur, regen en wind (richting + snelheid), om de 2 uur, 12
punten per dag. Sluit na 20 seconden vanzelf of bij een tik op de
overlay zelf.
Backend: Open-Meteo levert hourly-data in dezelfde call als daily
(geen extra verzoek, dezelfde 60min-cache). Nieuwe HourlyForecastMapper
groepeert per kalenderdatum en bemonstert om het andere uur, robuust
voor de zomertijddag (23/25 uur). OpenMeteoClient::dailyForecast()
hernoemd naar forecast(), geeft nu {daily, hourly} terug. Elke dag in
de forecast-array krijgt een hourly-lijst.
Frontend: temperatuurlijn hergebruikt sparklinePath (nu ook als
sparklinePoints voor exacte uitlijning met de 12-koloms grid), regen
als kleine staafjes, windpijl (nieuw icoon) wijst waarheen de wind
waait. 20s-balk via CSS transition met reflow-restart. Servicewerker
naar v5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
104 lines
3.6 KiB
PHP
104 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Weerscherm\Tests;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use Weerscherm\HourlyForecastMapper;
|
|
|
|
final class HourlyForecastMapperTest extends TestCase
|
|
{
|
|
private \DateTimeZone $tz;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->tz = new \DateTimeZone('Europe/Berlin');
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function hourly(): array
|
|
{
|
|
return json_decode((string) file_get_contents(__DIR__ . '/fixtures/forecast.json'), true)['hourly'];
|
|
}
|
|
|
|
public function testGroupsByDateAndSamplesEveryOtherHour(): void
|
|
{
|
|
$byDate = HourlyForecastMapper::mapByDate($this->hourly(), $this->tz);
|
|
|
|
self::assertSame(['2026-09-11', '2026-09-12', '2026-09-13', '2026-09-14', '2026-09-15', '2026-09-16'], array_keys($byDate));
|
|
self::assertCount(12, $byDate['2026-09-11']);
|
|
|
|
$hours = array_map(
|
|
fn(array $p) => (int) (new \DateTimeImmutable($p['at']))->format('H'),
|
|
$byDate['2026-09-11'],
|
|
);
|
|
self::assertSame([0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22], $hours);
|
|
}
|
|
|
|
public function testPointShapeAndValues(): void
|
|
{
|
|
$byDate = HourlyForecastMapper::mapByDate($this->hourly(), $this->tz);
|
|
$noon = $byDate['2026-09-11'][6]; // index 6 = 12:00 (0,2,4,6,8,10,12,...)
|
|
|
|
self::assertSame(['at', 'temp', 'precip_mm', 'wind_kmh', 'wind_dir'], array_keys($noon));
|
|
self::assertSame('2026-09-11T12:00:00+02:00', $noon['at']);
|
|
self::assertIsFloat($noon['temp']);
|
|
self::assertIsFloat($noon['precip_mm']);
|
|
self::assertIsFloat($noon['wind_kmh']);
|
|
self::assertIsInt($noon['wind_dir']);
|
|
}
|
|
|
|
public function testRainOnlyOnTheHoursThatHaveIt(): void
|
|
{
|
|
$byDate = HourlyForecastMapper::mapByDate($this->hourly(), $this->tz);
|
|
// 2026-09-14 heeft 8,2 mm in de dagfixture, verdeeld over 14-16u; de bemonsterde uren zijn 0,2,4,..22.
|
|
$rainy = array_filter($byDate['2026-09-14'], fn(array $p) => $p['precip_mm'] > 0);
|
|
|
|
self::assertNotEmpty($rainy);
|
|
foreach ($rainy as $p) {
|
|
self::assertGreaterThanOrEqual(14, (int) (new \DateTimeImmutable($p['at']))->format('H'));
|
|
}
|
|
}
|
|
|
|
public function testDaylightSavingDayWithTwentyFiveHoursSamplesWithoutError(): void
|
|
{
|
|
$hourly = json_decode((string) file_get_contents(__DIR__ . '/fixtures/hourly-dst.json'), true);
|
|
|
|
$byDate = HourlyForecastMapper::mapByDate($hourly, $this->tz);
|
|
|
|
self::assertCount(13, $byDate['2026-10-25'], '25 punten, om de 2: index 0,2,...,24 = 13 stuks');
|
|
}
|
|
|
|
public function testNullValuesStayNull(): void
|
|
{
|
|
$hourly = $this->hourly();
|
|
$hourly['temperature_2m'][0] = null;
|
|
$hourly['wind_direction_10m'][0] = 'kapot';
|
|
|
|
$byDate = HourlyForecastMapper::mapByDate($hourly, $this->tz);
|
|
|
|
self::assertNull($byDate['2026-09-11'][0]['temp']);
|
|
self::assertNull($byDate['2026-09-11'][0]['wind_dir']);
|
|
}
|
|
|
|
public function testMalformedTimestampsAreSkipped(): void
|
|
{
|
|
$byDate = HourlyForecastMapper::mapByDate([
|
|
'time' => ['niet-een-datum', '2026-09-11T05:00'],
|
|
'temperature_2m' => [1.0, 2.0],
|
|
'precipitation' => [0.0, 0.0],
|
|
'wind_speed_10m' => [1.0, 1.0],
|
|
'wind_direction_10m' => [1, 1],
|
|
], $this->tz);
|
|
|
|
self::assertSame(['2026-09-11'], array_keys($byDate));
|
|
self::assertCount(1, $byDate['2026-09-11']);
|
|
}
|
|
|
|
public function testMissingTimeArrayGivesEmptyResult(): void
|
|
{
|
|
self::assertSame([], HourlyForecastMapper::mapByDate([], $this->tz));
|
|
}
|
|
}
|