1
0

EventsSearchProviderTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\DAV\Tests\unit\Search;
  8. use OCA\DAV\CalDAV\CalDavBackend;
  9. use OCA\DAV\Search\EventsSearchProvider;
  10. use OCP\App\IAppManager;
  11. use OCP\IL10N;
  12. use OCP\IURLGenerator;
  13. use OCP\IUser;
  14. use OCP\Search\IFilter;
  15. use OCP\Search\ISearchQuery;
  16. use OCP\Search\SearchResult;
  17. use OCP\Search\SearchResultEntry;
  18. use Sabre\VObject\Reader;
  19. use Test\TestCase;
  20. class EventsSearchProviderTest extends TestCase {
  21. /** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */
  22. private $appManager;
  23. /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
  24. private $l10n;
  25. /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
  26. private $urlGenerator;
  27. /** @var CalDavBackend|\PHPUnit\Framework\MockObject\MockObject */
  28. private $backend;
  29. /** @var EventsSearchProvider */
  30. private $provider;
  31. // NO SUMMARY
  32. private $vEvent0 = 'BEGIN:VCALENDAR' . PHP_EOL .
  33. 'VERSION:2.0' . PHP_EOL .
  34. 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL .
  35. 'CALSCALE:GREGORIAN' . PHP_EOL .
  36. 'BEGIN:VEVENT' . PHP_EOL .
  37. 'CREATED:20161004T144433Z' . PHP_EOL .
  38. 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL .
  39. 'DTEND;VALUE=DATE:20161008' . PHP_EOL .
  40. 'TRANSP:TRANSPARENT' . PHP_EOL .
  41. 'DTSTART;VALUE=DATE:20161005' . PHP_EOL .
  42. 'DTSTAMP:20161004T144437Z' . PHP_EOL .
  43. 'SEQUENCE:0' . PHP_EOL .
  44. 'END:VEVENT' . PHP_EOL .
  45. 'END:VCALENDAR';
  46. // TIMED SAME DAY
  47. private $vEvent1 = 'BEGIN:VCALENDAR' . PHP_EOL .
  48. 'VERSION:2.0' . PHP_EOL .
  49. 'PRODID:-//Tests//' . PHP_EOL .
  50. 'CALSCALE:GREGORIAN' . PHP_EOL .
  51. 'BEGIN:VTIMEZONE' . PHP_EOL .
  52. 'TZID:Europe/Berlin' . PHP_EOL .
  53. 'BEGIN:DAYLIGHT' . PHP_EOL .
  54. 'TZOFFSETFROM:+0100' . PHP_EOL .
  55. 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL .
  56. 'DTSTART:19810329T020000' . PHP_EOL .
  57. 'TZNAME:GMT+2' . PHP_EOL .
  58. 'TZOFFSETTO:+0200' . PHP_EOL .
  59. 'END:DAYLIGHT' . PHP_EOL .
  60. 'BEGIN:STANDARD' . PHP_EOL .
  61. 'TZOFFSETFROM:+0200' . PHP_EOL .
  62. 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL .
  63. 'DTSTART:19961027T030000' . PHP_EOL .
  64. 'TZNAME:GMT+1' . PHP_EOL .
  65. 'TZOFFSETTO:+0100' . PHP_EOL .
  66. 'END:STANDARD' . PHP_EOL .
  67. 'END:VTIMEZONE' . PHP_EOL .
  68. 'BEGIN:VEVENT' . PHP_EOL .
  69. 'CREATED:20160809T163629Z' . PHP_EOL .
  70. 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL .
  71. 'DTEND;TZID=Europe/Berlin:20160816T100000' . PHP_EOL .
  72. 'TRANSP:OPAQUE' . PHP_EOL .
  73. 'SUMMARY:Test Europe Berlin' . PHP_EOL .
  74. 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL .
  75. 'DTSTAMP:20160809T163632Z' . PHP_EOL .
  76. 'SEQUENCE:0' . PHP_EOL .
  77. 'END:VEVENT' . PHP_EOL .
  78. 'END:VCALENDAR';
  79. // TIMED DIFFERENT DAY
  80. private $vEvent2 = 'BEGIN:VCALENDAR' . PHP_EOL .
  81. 'VERSION:2.0' . PHP_EOL .
  82. 'PRODID:-//Tests//' . PHP_EOL .
  83. 'CALSCALE:GREGORIAN' . PHP_EOL .
  84. 'BEGIN:VTIMEZONE' . PHP_EOL .
  85. 'TZID:Europe/Berlin' . PHP_EOL .
  86. 'BEGIN:DAYLIGHT' . PHP_EOL .
  87. 'TZOFFSETFROM:+0100' . PHP_EOL .
  88. 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL .
  89. 'DTSTART:19810329T020000' . PHP_EOL .
  90. 'TZNAME:GMT+2' . PHP_EOL .
  91. 'TZOFFSETTO:+0200' . PHP_EOL .
  92. 'END:DAYLIGHT' . PHP_EOL .
  93. 'BEGIN:STANDARD' . PHP_EOL .
  94. 'TZOFFSETFROM:+0200' . PHP_EOL .
  95. 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL .
  96. 'DTSTART:19961027T030000' . PHP_EOL .
  97. 'TZNAME:GMT+1' . PHP_EOL .
  98. 'TZOFFSETTO:+0100' . PHP_EOL .
  99. 'END:STANDARD' . PHP_EOL .
  100. 'END:VTIMEZONE' . PHP_EOL .
  101. 'BEGIN:VEVENT' . PHP_EOL .
  102. 'CREATED:20160809T163629Z' . PHP_EOL .
  103. 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL .
  104. 'DTEND;TZID=Europe/Berlin:20160817T100000' . PHP_EOL .
  105. 'TRANSP:OPAQUE' . PHP_EOL .
  106. 'SUMMARY:Test Europe Berlin' . PHP_EOL .
  107. 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL .
  108. 'DTSTAMP:20160809T163632Z' . PHP_EOL .
  109. 'SEQUENCE:0' . PHP_EOL .
  110. 'END:VEVENT' . PHP_EOL .
  111. 'END:VCALENDAR';
  112. // ALL-DAY ONE-DAY
  113. private $vEvent3 = 'BEGIN:VCALENDAR' . PHP_EOL .
  114. 'VERSION:2.0' . PHP_EOL .
  115. 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL .
  116. 'CALSCALE:GREGORIAN' . PHP_EOL .
  117. 'BEGIN:VEVENT' . PHP_EOL .
  118. 'CREATED:20161004T144433Z' . PHP_EOL .
  119. 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL .
  120. 'DTEND;VALUE=DATE:20161006' . PHP_EOL .
  121. 'TRANSP:TRANSPARENT' . PHP_EOL .
  122. 'DTSTART;VALUE=DATE:20161005' . PHP_EOL .
  123. 'DTSTAMP:20161004T144437Z' . PHP_EOL .
  124. 'SEQUENCE:0' . PHP_EOL .
  125. 'END:VEVENT' . PHP_EOL .
  126. 'END:VCALENDAR';
  127. // ALL-DAY MULTIPLE DAYS
  128. private $vEvent4 = 'BEGIN:VCALENDAR' . PHP_EOL .
  129. 'VERSION:2.0' . PHP_EOL .
  130. 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL .
  131. 'CALSCALE:GREGORIAN' . PHP_EOL .
  132. 'BEGIN:VEVENT' . PHP_EOL .
  133. 'CREATED:20161004T144433Z' . PHP_EOL .
  134. 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL .
  135. 'DTEND;VALUE=DATE:20161008' . PHP_EOL .
  136. 'TRANSP:TRANSPARENT' . PHP_EOL .
  137. 'DTSTART;VALUE=DATE:20161005' . PHP_EOL .
  138. 'DTSTAMP:20161004T144437Z' . PHP_EOL .
  139. 'SEQUENCE:0' . PHP_EOL .
  140. 'END:VEVENT' . PHP_EOL .
  141. 'END:VCALENDAR';
  142. // DURATION
  143. private $vEvent5 = 'BEGIN:VCALENDAR' . PHP_EOL .
  144. 'VERSION:2.0' . PHP_EOL .
  145. 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL .
  146. 'CALSCALE:GREGORIAN' . PHP_EOL .
  147. 'BEGIN:VEVENT' . PHP_EOL .
  148. 'CREATED:20161004T144433Z' . PHP_EOL .
  149. 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL .
  150. 'DURATION:P5D' . PHP_EOL .
  151. 'TRANSP:TRANSPARENT' . PHP_EOL .
  152. 'DTSTART;VALUE=DATE:20161005' . PHP_EOL .
  153. 'DTSTAMP:20161004T144437Z' . PHP_EOL .
  154. 'SEQUENCE:0' . PHP_EOL .
  155. 'END:VEVENT' . PHP_EOL .
  156. 'END:VCALENDAR';
  157. // NO DTEND - DATE
  158. private $vEvent6 = 'BEGIN:VCALENDAR' . PHP_EOL .
  159. 'VERSION:2.0' . PHP_EOL .
  160. 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL .
  161. 'CALSCALE:GREGORIAN' . PHP_EOL .
  162. 'BEGIN:VEVENT' . PHP_EOL .
  163. 'CREATED:20161004T144433Z' . PHP_EOL .
  164. 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL .
  165. 'TRANSP:TRANSPARENT' . PHP_EOL .
  166. 'DTSTART;VALUE=DATE:20161005' . PHP_EOL .
  167. 'DTSTAMP:20161004T144437Z' . PHP_EOL .
  168. 'SEQUENCE:0' . PHP_EOL .
  169. 'END:VEVENT' . PHP_EOL .
  170. 'END:VCALENDAR';
  171. // NO DTEND - DATE-TIME
  172. private $vEvent7 = 'BEGIN:VCALENDAR' . PHP_EOL .
  173. 'VERSION:2.0' . PHP_EOL .
  174. 'PRODID:-//Tests//' . PHP_EOL .
  175. 'CALSCALE:GREGORIAN' . PHP_EOL .
  176. 'BEGIN:VTIMEZONE' . PHP_EOL .
  177. 'TZID:Europe/Berlin' . PHP_EOL .
  178. 'BEGIN:DAYLIGHT' . PHP_EOL .
  179. 'TZOFFSETFROM:+0100' . PHP_EOL .
  180. 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL .
  181. 'DTSTART:19810329T020000' . PHP_EOL .
  182. 'TZNAME:GMT+2' . PHP_EOL .
  183. 'TZOFFSETTO:+0200' . PHP_EOL .
  184. 'END:DAYLIGHT' . PHP_EOL .
  185. 'BEGIN:STANDARD' . PHP_EOL .
  186. 'TZOFFSETFROM:+0200' . PHP_EOL .
  187. 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL .
  188. 'DTSTART:19961027T030000' . PHP_EOL .
  189. 'TZNAME:GMT+1' . PHP_EOL .
  190. 'TZOFFSETTO:+0100' . PHP_EOL .
  191. 'END:STANDARD' . PHP_EOL .
  192. 'END:VTIMEZONE' . PHP_EOL .
  193. 'BEGIN:VEVENT' . PHP_EOL .
  194. 'CREATED:20160809T163629Z' . PHP_EOL .
  195. 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL .
  196. 'TRANSP:OPAQUE' . PHP_EOL .
  197. 'SUMMARY:Test Europe Berlin' . PHP_EOL .
  198. 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL .
  199. 'DTSTAMP:20160809T163632Z' . PHP_EOL .
  200. 'SEQUENCE:0' . PHP_EOL .
  201. 'END:VEVENT' . PHP_EOL .
  202. 'END:VCALENDAR';
  203. protected function setUp(): void {
  204. parent::setUp();
  205. $this->appManager = $this->createMock(IAppManager::class);
  206. $this->l10n = $this->createMock(IL10N::class);
  207. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  208. $this->backend = $this->createMock(CalDavBackend::class);
  209. $this->provider = new EventsSearchProvider(
  210. $this->appManager,
  211. $this->l10n,
  212. $this->urlGenerator,
  213. $this->backend
  214. );
  215. }
  216. public function testGetId(): void {
  217. $this->assertEquals('calendar', $this->provider->getId());
  218. }
  219. public function testGetName(): void {
  220. $this->l10n->expects($this->exactly(1))
  221. ->method('t')
  222. ->with('Events')
  223. ->willReturnArgument(0);
  224. $this->assertEquals('Events', $this->provider->getName());
  225. }
  226. public function testSearchAppDisabled(): void {
  227. $user = $this->createMock(IUser::class);
  228. $query = $this->createMock(ISearchQuery::class);
  229. $this->appManager->expects($this->once())
  230. ->method('isEnabledForUser')
  231. ->with('calendar', $user)
  232. ->willReturn(false);
  233. $this->l10n->expects($this->exactly(1))
  234. ->method('t')
  235. ->willReturnArgument(0);
  236. $this->backend->expects($this->never())
  237. ->method('getCalendarsForUser');
  238. $this->backend->expects($this->never())
  239. ->method('getSubscriptionsForUser');
  240. $this->backend->expects($this->never())
  241. ->method('searchPrincipalUri');
  242. $actual = $this->provider->search($user, $query);
  243. $data = $actual->jsonSerialize();
  244. $this->assertInstanceOf(SearchResult::class, $actual);
  245. $this->assertEquals('Events', $data['name']);
  246. $this->assertEmpty($data['entries']);
  247. $this->assertFalse($data['isPaginated']);
  248. $this->assertNull($data['cursor']);
  249. }
  250. public function testSearch(): void {
  251. $user = $this->createMock(IUser::class);
  252. $user->method('getUID')->willReturn('john.doe');
  253. $query = $this->createMock(ISearchQuery::class);
  254. $seachTermFilter = $this->createMock(IFilter::class);
  255. $query->method('getFilter')->willReturnCallback(function ($name) use ($seachTermFilter) {
  256. return match ($name) {
  257. 'term' => $seachTermFilter,
  258. default => null,
  259. };
  260. });
  261. $seachTermFilter->method('get')->willReturn('search term');
  262. $query->method('getLimit')->willReturn(5);
  263. $query->method('getCursor')->willReturn(20);
  264. $this->appManager->expects($this->once())
  265. ->method('isEnabledForUser')
  266. ->with('calendar', $user)
  267. ->willReturn(true);
  268. $this->l10n->method('t')->willReturnArgument(0);
  269. $this->backend->expects($this->once())
  270. ->method('getCalendarsForUser')
  271. ->with('principals/users/john.doe')
  272. ->willReturn([
  273. [
  274. 'id' => 99,
  275. 'principaluri' => 'principals/users/john.doe',
  276. 'uri' => 'calendar-uri-99',
  277. ], [
  278. 'id' => 123,
  279. 'principaluri' => 'principals/users/john.doe',
  280. 'uri' => 'calendar-uri-123',
  281. ]
  282. ]);
  283. $this->backend->expects($this->once())
  284. ->method('getSubscriptionsForUser')
  285. ->with('principals/users/john.doe')
  286. ->willReturn([
  287. [
  288. 'id' => 1337,
  289. 'principaluri' => 'principals/users/john.doe',
  290. 'uri' => 'subscription-uri-1337',
  291. ]
  292. ]);
  293. $this->backend->expects($this->once())
  294. ->method('searchPrincipalUri')
  295. ->with('principals/users/john.doe', 'search term', ['VEVENT'],
  296. ['SUMMARY', 'LOCATION', 'DESCRIPTION', 'ATTENDEE', 'ORGANIZER', 'CATEGORIES'],
  297. ['ATTENDEE' => ['CN'], 'ORGANIZER' => ['CN']],
  298. ['limit' => 5, 'offset' => 20, 'timerange' => ['start' => null, 'end' => null]])
  299. ->willReturn([
  300. [
  301. 'calendarid' => 99,
  302. 'calendartype' => CalDavBackend::CALENDAR_TYPE_CALENDAR,
  303. 'uri' => 'event0.ics',
  304. 'calendardata' => $this->vEvent0,
  305. ],
  306. [
  307. 'calendarid' => 123,
  308. 'calendartype' => CalDavBackend::CALENDAR_TYPE_CALENDAR,
  309. 'uri' => 'event1.ics',
  310. 'calendardata' => $this->vEvent1,
  311. ],
  312. [
  313. 'calendarid' => 1337,
  314. 'calendartype' => CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION,
  315. 'uri' => 'event2.ics',
  316. 'calendardata' => $this->vEvent2,
  317. ]
  318. ]);
  319. $provider = $this->getMockBuilder(EventsSearchProvider::class)
  320. ->setConstructorArgs([
  321. $this->appManager,
  322. $this->l10n,
  323. $this->urlGenerator,
  324. $this->backend,
  325. ])
  326. ->setMethods([
  327. 'getDeepLinkToCalendarApp',
  328. 'generateSubline',
  329. ])
  330. ->getMock();
  331. $provider->expects($this->exactly(3))
  332. ->method('generateSubline')
  333. ->willReturn('subline');
  334. $provider->expects($this->exactly(3))
  335. ->method('getDeepLinkToCalendarApp')
  336. ->withConsecutive(
  337. ['principals/users/john.doe', 'calendar-uri-99', 'event0.ics'],
  338. ['principals/users/john.doe', 'calendar-uri-123', 'event1.ics'],
  339. ['principals/users/john.doe', 'subscription-uri-1337', 'event2.ics']
  340. )
  341. ->willReturn('deep-link-to-calendar');
  342. $actual = $provider->search($user, $query);
  343. $data = $actual->jsonSerialize();
  344. $this->assertInstanceOf(SearchResult::class, $actual);
  345. $this->assertEquals('Events', $data['name']);
  346. $this->assertCount(3, $data['entries']);
  347. $this->assertTrue($data['isPaginated']);
  348. $this->assertEquals(23, $data['cursor']);
  349. $result0 = $data['entries'][0];
  350. $result0Data = $result0->jsonSerialize();
  351. $result1 = $data['entries'][1];
  352. $result1Data = $result1->jsonSerialize();
  353. $result2 = $data['entries'][2];
  354. $result2Data = $result2->jsonSerialize();
  355. $this->assertInstanceOf(SearchResultEntry::class, $result0);
  356. $this->assertEmpty($result0Data['thumbnailUrl']);
  357. $this->assertEquals('Untitled event', $result0Data['title']);
  358. $this->assertEquals('subline', $result0Data['subline']);
  359. $this->assertEquals('deep-link-to-calendar', $result0Data['resourceUrl']);
  360. $this->assertEquals('icon-calendar-dark', $result0Data['icon']);
  361. $this->assertFalse($result0Data['rounded']);
  362. $this->assertInstanceOf(SearchResultEntry::class, $result1);
  363. $this->assertEmpty($result1Data['thumbnailUrl']);
  364. $this->assertEquals('Test Europe Berlin', $result1Data['title']);
  365. $this->assertEquals('subline', $result1Data['subline']);
  366. $this->assertEquals('deep-link-to-calendar', $result1Data['resourceUrl']);
  367. $this->assertEquals('icon-calendar-dark', $result1Data['icon']);
  368. $this->assertFalse($result1Data['rounded']);
  369. $this->assertInstanceOf(SearchResultEntry::class, $result2);
  370. $this->assertEmpty($result2Data['thumbnailUrl']);
  371. $this->assertEquals('Test Europe Berlin', $result2Data['title']);
  372. $this->assertEquals('subline', $result2Data['subline']);
  373. $this->assertEquals('deep-link-to-calendar', $result2Data['resourceUrl']);
  374. $this->assertEquals('icon-calendar-dark', $result2Data['icon']);
  375. $this->assertFalse($result2Data['rounded']);
  376. }
  377. public function testGetDeepLinkToCalendarApp(): void {
  378. $this->urlGenerator->expects($this->once())
  379. ->method('linkTo')
  380. ->with('', 'remote.php')
  381. ->willReturn('link-to-remote.php');
  382. $this->urlGenerator->expects($this->once())
  383. ->method('linkToRoute')
  384. ->with('calendar.view.index')
  385. ->willReturn('link-to-route-calendar/');
  386. $this->urlGenerator->expects($this->once())
  387. ->method('getAbsoluteURL')
  388. ->with('link-to-route-calendar/edit/bGluay10by1yZW1vdGUucGhwL2Rhdi9jYWxlbmRhcnMvam9obi5kb2UvZm9vL2Jhci5pY3M=')
  389. ->willReturn('absolute-url-to-route');
  390. $actual = self::invokePrivate($this->provider, 'getDeepLinkToCalendarApp', ['principals/users/john.doe', 'foo', 'bar.ics']);
  391. $this->assertEquals('absolute-url-to-route', $actual);
  392. }
  393. /**
  394. * @param string $ics
  395. * @param string $expectedSubline
  396. *
  397. * @dataProvider generateSublineDataProvider
  398. */
  399. public function testGenerateSubline(string $ics, string $expectedSubline): void {
  400. $vCalendar = Reader::read($ics, Reader::OPTION_FORGIVING);
  401. $eventComponent = $vCalendar->VEVENT;
  402. $this->l10n->method('l')
  403. ->willReturnCallback(static function (string $type, \DateTime $date, $_):string {
  404. if ($type === 'time') {
  405. return $date->format('H:i');
  406. }
  407. return $date->format('m-d');
  408. });
  409. $actual = self::invokePrivate($this->provider, 'generateSubline', [$eventComponent]);
  410. $this->assertEquals($expectedSubline, $actual);
  411. }
  412. public function generateSublineDataProvider(): array {
  413. return [
  414. [$this->vEvent1, '08-16 09:00 - 10:00'],
  415. [$this->vEvent2, '08-16 09:00 - 08-17 10:00'],
  416. [$this->vEvent3, '10-05'],
  417. [$this->vEvent4, '10-05 - 10-07'],
  418. [$this->vEvent5, '10-05 - 10-09'],
  419. [$this->vEvent6, '10-05'],
  420. [$this->vEvent7, '08-16 09:00 - 09:00'],
  421. ];
  422. }
  423. }