{getIcsUrl()}
+ + {__('To subscribe in Google Calendar: open Other calendars → From URL, then paste the link above. New events will sync automatically.', 'mayo-events-manager')} +
+This is bold text.
', ]); - - $this->setPostMeta(204, [ - 'event_type' => 'Service', - 'event_start_date' => date('Y-m-d', strtotime('+14 days')), - 'event_end_date' => date('Y-m-d', strtotime('+14 days')), + $this->setPostMeta(109, [ + 'event_type' => 'Convention', + 'event_start_date' => '2030-01-01', + 'event_end_date' => '2030-01-01', 'event_start_time' => '10:00:00', - 'event_end_time' => '14:00:00', - 'timezone' => 'UTC' + 'event_end_time' => '12:00:00', + 'timezone' => 'UTC', + 'location_name' => 'Big Hall', ]); - Functions\when('get_posts')->justReturn([$post1, $post2]); - - $method = $this->getPrivateMethod('get_ics_items'); - $result = $method->invoke(null); + $result = $this->buildVevent($post); + $this->assertStringContainsString('Event Type: Convention', $result['ics']); + $this->assertStringContainsString('Location: Big Hall', $result['ics']); + $this->assertStringContainsString('This is bold text', $result['ics']); + $this->assertStringNotContainsString('', $result['ics']); + } - $this->assertCount(2, $result); - $this->assertEquals('First Event', $result[0]['summary']); - $this->assertEquals('Second Event', $result[1]['summary']); + public function testBuildRruleReturnsNullForUnknownType(): void { + $method = $this->getPrivateMethod('build_rrule'); + $tz = new \DateTimeZone('UTC'); + $this->assertNull($method->invoke(null, ['type' => 'mystery'], $tz)); + $this->assertNull($method->invoke(null, ['type' => 'none'], $tz)); } - /** - * Test escape_ical_text handles empty string - */ - public function testEscapeIcalTextHandlesEmptyString(): void { - $method = $this->getPrivateMethod('escape_ical_text'); + public function testBuildVtimezoneIncludesTzidLine(): void { + $method = $this->getPrivateMethod('build_vtimezone'); + $out = $method->invoke(null, 'America/New_York'); - $result = $method->invoke(null, ''); + $this->assertStringContainsString('BEGIN:VTIMEZONE', $out); + $this->assertStringContainsString('TZID:America/New_York', $out); + $this->assertStringContainsString('END:VTIMEZONE', $out); + // America/New_York observes DST, so we should get both components. + $this->assertStringContainsString('BEGIN:STANDARD', $out); + $this->assertStringContainsString('BEGIN:DAYLIGHT', $out); + } - $this->assertEquals('', $result); + public function testFormatOffsetProducesIcsOffset(): void { + $method = $this->getPrivateMethod('format_offset'); + $this->assertEquals('+0530', $method->invoke(null, 5 * 3600 + 30 * 60)); + $this->assertEquals('-0500', $method->invoke(null, -5 * 3600)); + $this->assertEquals('+0000', $method->invoke(null, 0)); } - /** - * Test escape_ical_text handles multiple line breaks - */ - public function testEscapeIcalTextHandlesMultipleLineBreaks(): void { - $method = $this->getPrivateMethod('escape_ical_text'); + public function testQueryEventsHandlesGetPostsError(): void { + Functions\when('get_posts')->justReturn(new \WP_Error('error', 'Database error')); - $result = $method->invoke(null, "Line 1\n\nLine 2\n\n\nLine 3"); + $method = $this->getPrivateMethod('query_events'); + $result = $method->invoke(null, '', '', 'AND', '', ''); - // All newlines should be escaped - $this->assertStringNotContainsString("\n", $result); + $this->assertIsArray($result); + $this->assertEmpty($result); } /** - * Test get_ics_items strips HTML from content + * Helper: invoke build_vevent with the standard host. */ - public function testGetIcsItemsStripsHtmlFromContent(): void { - $post = $this->createMockPost([ - 'ID' => 205, - 'post_title' => 'Event With HTML', - 'post_content' => 'This is bold text with links.
', - 'post_type' => 'mayo_event' - ]); - - $this->setPostMeta(205, [ - 'event_type' => 'Meeting', - 'event_start_date' => date('Y-m-d', strtotime('+8 days')), - 'event_end_date' => date('Y-m-d', strtotime('+8 days')), - 'event_start_time' => '19:00:00', - 'event_end_time' => '21:00:00', - 'timezone' => 'UTC' - ]); - - Functions\when('get_posts')->justReturn([$post]); - - $method = $this->getPrivateMethod('get_ics_items'); - $result = $method->invoke(null); - - $this->assertCount(1, $result); - // HTML should be stripped - $this->assertStringNotContainsString('', $result[0]['description']); - $this->assertStringNotContainsString('', $result[0]['description']); - $this->assertStringContainsString('bold', $result[0]['description']); + private function buildVevent($post): ?array { + $method = $this->getPrivateMethod('build_vevent'); + return $method->invoke(null, $post, 'example.com'); } - /** - * Helper to get private method - */ private function getPrivateMethod(string $methodName): \ReflectionMethod { $reflection = new ReflectionClass(CalendarFeed::class); - $method = $reflection->getMethod($methodName); - $method->setAccessible(true); - return $method; + return $reflection->getMethod($methodName); } }