223 lines
8.4 KiB
PHP
223 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace dokuwiki\plugin\luxtools\test;
|
|
|
|
use dokuwiki\plugin\luxtools\ChronologicalIcsEvents;
|
|
use DokuWikiTest;
|
|
|
|
require_once(__DIR__ . '/../autoload.php');
|
|
|
|
/**
|
|
* Tests for local ICS event parsing.
|
|
*
|
|
* @group plugin_luxtools
|
|
* @group plugins
|
|
*/
|
|
class ChronologicalIcsEventsTest extends DokuWikiTest
|
|
{
|
|
public function testEventsForDateParsesAllDayAndTimedEntries(): void
|
|
{
|
|
$dir = TMP_DIR . '/chrono_ics/' . uniqid('case_', true);
|
|
@mkdir($dir, 0777, true);
|
|
$ics = $dir . '/calendar.ics';
|
|
|
|
$content = "BEGIN:VCALENDAR\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "DTSTART;VALUE=DATE:20260216\n"
|
|
. "SUMMARY:Ganztag Event\n"
|
|
. "END:VEVENT\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "DTSTART:20260216T134500\n"
|
|
. "SUMMARY:Termin A\n"
|
|
. "END:VEVENT\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "DTSTART:20260217T090000\n"
|
|
. "SUMMARY:Anderer Tag\n"
|
|
. "END:VEVENT\n"
|
|
. "END:VCALENDAR\n";
|
|
@file_put_contents($ics, $content);
|
|
|
|
$events = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-16');
|
|
if (count($events) !== 2) {
|
|
throw new \Exception('Expected 2 events for 2026-02-16, got ' . count($events));
|
|
}
|
|
|
|
$summaries = array_map(static fn(array $e): string => (string)$e['summary'], $events);
|
|
if (!in_array('Ganztag Event', $summaries, true)) {
|
|
throw new \Exception('Missing all-day event summary');
|
|
}
|
|
if (!in_array('Termin A', $summaries, true)) {
|
|
throw new \Exception('Missing timed event summary');
|
|
}
|
|
|
|
$timed = null;
|
|
foreach ($events as $event) {
|
|
if ((string)($event['summary'] ?? '') === 'Termin A') {
|
|
$timed = $event;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!is_array($timed)) {
|
|
throw new \Exception('Timed event payload missing');
|
|
}
|
|
if (trim((string)($timed['startIso'] ?? '')) === '') {
|
|
throw new \Exception('Timed event should expose startIso for client-side timezone conversion');
|
|
}
|
|
}
|
|
|
|
public function testEventsForDateReadsMultipleConfiguredFiles(): void
|
|
{
|
|
$dir = TMP_DIR . '/chrono_ics/' . uniqid('multi_', true);
|
|
@mkdir($dir, 0777, true);
|
|
$ics1 = $dir . '/one.ics';
|
|
$ics2 = $dir . '/two.ics';
|
|
|
|
@file_put_contents($ics1, "BEGIN:VCALENDAR\nBEGIN:VEVENT\nDTSTART:20260218T100000\nSUMMARY:Eins\nEND:VEVENT\nEND:VCALENDAR\n");
|
|
@file_put_contents($ics2, "BEGIN:VCALENDAR\nBEGIN:VEVENT\nDTSTART:20260218T110000\nSUMMARY:Zwei\nEND:VEVENT\nEND:VCALENDAR\n");
|
|
|
|
$config = $ics1 . "\n" . $ics2;
|
|
$events = ChronologicalIcsEvents::eventsForDate($config, '2026-02-18');
|
|
|
|
if (count($events) !== 2) {
|
|
throw new \Exception('Expected 2 events from two files, got ' . count($events));
|
|
}
|
|
}
|
|
|
|
public function testEventsForDateSupportsWeeklyRecurrence(): void
|
|
{
|
|
$dir = TMP_DIR . '/chrono_ics/' . uniqid('rrule_', true);
|
|
@mkdir($dir, 0777, true);
|
|
$ics = $dir . '/recurring.ics';
|
|
|
|
$content = "BEGIN:VCALENDAR\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "UID:weekly-1\n"
|
|
. "DTSTART:20260205T090000\n"
|
|
. "RRULE:FREQ=WEEKLY;INTERVAL=1\n"
|
|
. "SUMMARY:Wiederkehrender Termin\n"
|
|
. "END:VEVENT\n"
|
|
. "END:VCALENDAR\n";
|
|
@file_put_contents($ics, $content);
|
|
|
|
$events = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-12');
|
|
if (count($events) < 1) {
|
|
throw new \Exception('Expected recurring event on 2026-02-12, got none');
|
|
}
|
|
|
|
$summaries = array_map(static fn(array $e): string => (string)$e['summary'], $events);
|
|
if (!in_array('Wiederkehrender Termin', $summaries, true)) {
|
|
throw new \Exception('Recurring summary not found on matching date');
|
|
}
|
|
}
|
|
|
|
public function testEventsForDateRespectsExdateForRecurringEvent(): void
|
|
{
|
|
$dir = TMP_DIR . '/chrono_ics/' . uniqid('exdate_', true);
|
|
@mkdir($dir, 0777, true);
|
|
$ics = $dir . '/recurring-exdate.ics';
|
|
|
|
$content = "BEGIN:VCALENDAR\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "UID:weekly-2\n"
|
|
. "DTSTART:20260205T090000\n"
|
|
. "RRULE:FREQ=WEEKLY;COUNT=4\n"
|
|
. "EXDATE:20260212T090000\n"
|
|
. "SUMMARY:Termin mit Ausnahme\n"
|
|
. "END:VEVENT\n"
|
|
. "END:VCALENDAR\n";
|
|
@file_put_contents($ics, $content);
|
|
|
|
$events = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-12');
|
|
$summaries = array_map(static fn(array $e): string => (string)$e['summary'], $events);
|
|
if (in_array('Termin mit Ausnahme', $summaries, true)) {
|
|
throw new \Exception('Recurring event with EXDATE should not appear on excluded day');
|
|
}
|
|
}
|
|
|
|
public function testEventsForDateKeepsUtcDateAndTimeAsIs(): void
|
|
{
|
|
$previousTimezone = date_default_timezone_get();
|
|
date_default_timezone_set('Europe/Berlin');
|
|
|
|
try {
|
|
$dir = TMP_DIR . '/chrono_ics/' . uniqid('tz_', true);
|
|
@mkdir($dir, 0777, true);
|
|
$ics = $dir . '/timezone.ics';
|
|
|
|
$content = "BEGIN:VCALENDAR\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "UID:utc-shift\n"
|
|
. "DTSTART:20260216T233000Z\n"
|
|
. "SUMMARY:UTC Spaet\n"
|
|
. "END:VEVENT\n"
|
|
. "END:VCALENDAR\n";
|
|
@file_put_contents($ics, $content);
|
|
|
|
$eventsOn16 = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-16');
|
|
$eventsOn17 = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-17');
|
|
|
|
$summaries16 = array_map(static fn(array $e): string => (string)$e['summary'], $eventsOn16);
|
|
$summaries17 = array_map(static fn(array $e): string => (string)$e['summary'], $eventsOn17);
|
|
|
|
if (!in_array('UTC Spaet', $summaries16, true)) {
|
|
throw new \Exception('UTC event should stay on its own UTC date');
|
|
}
|
|
if (in_array('UTC Spaet', $summaries17, true)) {
|
|
throw new \Exception('UTC event should not be shifted to next day by server timezone');
|
|
}
|
|
|
|
$utcEvent = null;
|
|
foreach ($eventsOn16 as $entry) {
|
|
if ((string)($entry['summary'] ?? '') === 'UTC Spaet') {
|
|
$utcEvent = $entry;
|
|
break;
|
|
}
|
|
}
|
|
if (!is_array($utcEvent)) {
|
|
throw new \Exception('UTC event payload missing after day match');
|
|
}
|
|
if ((string)($utcEvent['time'] ?? '') !== '23:30') {
|
|
throw new \Exception('UTC event time should remain unchanged (expected 23:30)');
|
|
}
|
|
} finally {
|
|
date_default_timezone_set($previousTimezone);
|
|
}
|
|
}
|
|
|
|
public function testEventsForDateShowsMultiDayAllDayEventOnOverlappingDays(): void
|
|
{
|
|
$dir = TMP_DIR . '/chrono_ics/' . uniqid('multiday_', true);
|
|
@mkdir($dir, 0777, true);
|
|
$ics = $dir . '/multiday.ics';
|
|
|
|
$content = "BEGIN:VCALENDAR\n"
|
|
. "BEGIN:VEVENT\n"
|
|
. "UID:multi-day-1\n"
|
|
. "DTSTART;VALUE=DATE:20260216\n"
|
|
. "DTEND;VALUE=DATE:20260218\n"
|
|
. "SUMMARY:Mehrtagesereignis\n"
|
|
. "END:VEVENT\n"
|
|
. "END:VCALENDAR\n";
|
|
@file_put_contents($ics, $content);
|
|
|
|
$eventsOn16 = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-16');
|
|
$eventsOn17 = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-17');
|
|
$eventsOn18 = ChronologicalIcsEvents::eventsForDate($ics, '2026-02-18');
|
|
|
|
$summaries16 = array_map(static fn(array $e): string => (string)$e['summary'], $eventsOn16);
|
|
$summaries17 = array_map(static fn(array $e): string => (string)$e['summary'], $eventsOn17);
|
|
$summaries18 = array_map(static fn(array $e): string => (string)$e['summary'], $eventsOn18);
|
|
|
|
if (!in_array('Mehrtagesereignis', $summaries16, true)) {
|
|
throw new \Exception('Multi-day all-day event should appear on start day');
|
|
}
|
|
if (!in_array('Mehrtagesereignis', $summaries17, true)) {
|
|
throw new \Exception('Multi-day all-day event should appear on overlapping day');
|
|
}
|
|
if (in_array('Mehrtagesereignis', $summaries18, true)) {
|
|
throw new \Exception('Multi-day all-day event should respect exclusive DTEND day');
|
|
}
|
|
}
|
|
}
|