* @copyright 2007-2022 Kjell-Inge Gustafsson, kigkonsult, All rights reserved * @link https://kigkonsult.se * @license Subject matter of licence is the software iCalcreator. * The above copyright, link, package and version notices, * this licence notice and the invariant [rfc5545] PRODID result use * as implemented and invoked in iCalcreator shall be included in * all copies or substantial portions of the iCalcreator. * * iCalcreator is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * iCalcreator is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with iCalcreator. If not, see . */ namespace Kigkonsult\Icalcreator; use DateTime; use DateTimeInterface; use Exception; use IntlTimeZone; use Kigkonsult\Icalcreator\Util\DateTimeFactory; use Kigkonsult\Icalcreator\Util\DateTimeZoneFactory; use Kigkonsult\Icalcreator\Util\StringFactory; use Kigkonsult\Icalcreator\Util\Util; use Kigkonsult\Icalcreator\Xml\Formatter as XmlFormatter; use Kigkonsult\Icalcreator\Xml\Parser as XmlParser; use PHPUnit\Framework\TestCase; use SimpleXMLElement; /** * class DtBase * * @since 2.41.44 2022-04-27 */ abstract class DtBase extends TestCase { use GetPropMethodNamesTrait; protected static function getErrMsg( ? string $spec = null, int|string $case, string $testFcn, ? string $inst = null, ? string $method = null, mixed $inValue = null, mixed $inParams = null ) { static $ERRFMT = "Error %s in case #%s, %s <%s>->%s"; $output = sprintf( $ERRFMT, ( $spec ?? '' ), $case, $testFcn, $inst, $method ); if( ! empty( $inValue )) { $output .= PHP_EOL . ' inValue : ' . str_replace( PHP_EOL, '', var_export( $inValue, true )); } if( ! empty( $inParams )) { $output .= PHP_EOL . ' inParams : ' . str_replace( PHP_EOL, '', var_export( $inParams, true )); } return $output; } /** * The test method, case suffix '-1xx', test prop create-, delete-, get- is- and set-methods * * @param int $case * @param array $compsProps * @param mixed $value * @param mixed $params * @param Pc $expectedGet * @param string $expectedString * @throws Exception * @since 2.41.47 2022-04-29 */ public function thePropTest( int $case, array $compsProps, mixed $value, mixed $params, Pc $expectedGet, string $expectedString ) : void { // error_log( __METHOD__ . ' IN 1 ' . $case . ' ' . var_export( $value, true )); // test ### $c = new Vcalendar(); $pcInput = $firstLastmodifiedLoad = false; foreach( $compsProps as $theComp => $props ) { $newMethod = 'new' . $theComp; $comp = match ( true ) { IcalInterface::PARTICIPANT === $theComp => $c->newVevent()->{$newMethod}() ->setDtstamp( $value, $params ), IcalInterface::AVAILABLE === $theComp => $c->newVavailability()->{$newMethod}(), default => $c->{$newMethod}(), }; foreach( $props as $propName ) { [ $createMethod, $deleteMethod, $getMethod, $isMethod, $setMethod ] = self::getPropMethodnames( $propName ); if( IcalInterface::LAST_MODIFIED === $propName ) { if( ! $firstLastmodifiedLoad ) { $this->assertFalse( $c->{$isMethod}(), self::getErrMsg( null, $case . '-110', __FUNCTION__, Vcalendar::VCALENDAR, $isMethod ) . PHP_EOL . $c->createCalendar() // test ### ); } $c->setLastmodified( $value, $params ); $firstLastmodifiedLoad = true; $this->assertTrue( $c->{$isMethod}(), self::getErrMsg( null, $case . '-111', __FUNCTION__, Vcalendar::VCALENDAR, $isMethod ) ); } // end if last-mod... // error_log( __METHOD__ . ' #' . $case . ' start <' . $theComp . '>->' . $propName . ' value IN : ' . var_export( $value, true )); // test ### if( $propName !== IcalInterface::DTSTAMP ) { $this->assertFalse( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-112', __FUNCTION__, $theComp, $isMethod ) ); } if( in_array( $propName, [ IcalInterface::EXDATE, IcalInterface::RDATE ], true )) { $comp->{$setMethod}( [ $value ], $params ); $getValue = $comp->{$getMethod}( null, true ); if( ! empty( $getValue->value )) { $getValue->value = reset( $getValue->value ); } } else { if( in_array( $propName, [ IcalInterface::DTEND, IcalInterface::DUE, IcalInterface::RECURRENCE_ID, ], true ) ) { $comp->setDtstart( $value, $params ); } $comp->{$setMethod}( $value, $params ); $getValue = $comp->{$getMethod}( true ); } // end else if( null !== $value ) { $this->assertTrue( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-113', __FUNCTION__, $theComp, $isMethod ) ); } if( $expectedGet->value instanceof DateTime && $getValue->value instanceof DateTime ) { if( $expectedGet->hasParamKey( IcalInterface::TZID ) && $getValue->hasParamKey( IcalInterface::TZID )) { // has same offset (TZID might differ) $this->assertEquals( DateTimeFactory::factory( null, $expectedGet->getParams( IcalInterface::TZID ) )->getOffset(), DateTimeFactory::factory( null, $getValue->getParams( IcalInterface::TZID ) )->getOffset(), self::getErrMsg( null, $case . '-114-1', __FUNCTION__, $theComp, $getMethod ) ); } else { $getValue->removeParam( IcalInterface::ISLOCALTIME ); $this->assertEquals( var_export( $expectedGet->params, true ), var_export( $getValue->params, true ), self::getErrMsg( null, $case . '-114-2', __FUNCTION__, $theComp, $getMethod, $value, $params ) ); } $fmt = $expectedGet->hasParamValue( IcalInterface::DATE ) ? DateTimeFactory::$Ymd : DateTimeFactory::$YmdHis; $this->assertEquals( $expectedGet->value->format( $fmt ), $getValue->value->format( $fmt ), self::getErrMsg( null, $case . '-114-3', __FUNCTION__, $theComp, $getMethod, $value, $params ) ); $this->assertEquals( // in case of diff. timezones but with equal offset $expectedGet->value->getOffset(), $getValue->value->getOffset(), self::getErrMsg( null, $case . '-114-4', __FUNCTION__, $theComp, $getMethod, $value, $params ) ); } // end if ..DateTime.. $this->assertEquals( strtoupper( $propName ) . $expectedString, trim( $comp->{$createMethod}() ), self::getErrMsg( null, $case . '-116', __FUNCTION__, $theComp, $createMethod ) ); if( method_exists( $comp, $deleteMethod )) { // Dtstamp/Uid has NO deleteMethod $comp->{$deleteMethod}(); } if( IcalInterface::DTSTAMP === $propName ) { $this->assertTrue( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-117', __FUNCTION__, $theComp, $isMethod ) ); $this->assertNotFalse( $comp->{$getMethod}(), self::getErrMsg( '(after delete) ', $case . '-118', __FUNCTION__, $theComp, $getMethod ) ); } // end if else { $this->assertFalse( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-119', __FUNCTION__, $theComp, $isMethod ) ); $this->assertFalse( $comp->{$getMethod}(), self::getErrMsg( '(after delete) ', $case . '-120', __FUNCTION__, $theComp, $getMethod ) ); } // end else if( $pcInput ) { $comp->{$setMethod}( Pc::factory( $value, $params )); } else { $comp->{$setMethod}( $value, $params ); } if( null !== $value ) { $this->assertTrue( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-121', __FUNCTION__, $theComp, $isMethod ) ); } $pcInput = ! $pcInput; if( IcalInterface::Z === substr( $expectedString, -1 )) { $this->parseCalendarTest( $case, $c, $expectedString, $theComp, $propName ); // test ### } } // end foreach .. => propName } // end foreach comp => props $this->parseCalendarTest( $case, $c, $expectedString, $theComp, $propName ); } /** * The test method, case suffix '-2xx', test prop get--method without params * * @param int $case * @param array $compsProps * @param mixed $value * @param mixed $params * @param Pc $expectedGet * @throws Exception * @since 2.41.44 2022-04-21 */ public function propGetNoParamsTest( int $case, array $compsProps, mixed $value, mixed $params, Pc $expectedGet ) : void { $c = new Vcalendar(); foreach( $compsProps as $theComp => $props ) { $newMethod = 'new' . $theComp; $comp = match ( true ) { IcalInterface::PARTICIPANT === $theComp => $c->newVevent()->{$newMethod}() ->setDtstamp( $value, $params ), IcalInterface::AVAILABLE === $theComp => $c->newVavailability()->{$newMethod}(), default => $c->{$newMethod}(), }; foreach( $props as $propName ) { [ , , $getMethod, $isMethod, $setMethod ] = self::getPropMethodnames( $propName ); if( IcalInterface::DTSTAMP !== $propName ) { $this->assertFalse( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-211', __FUNCTION__, $theComp, $isMethod ) ); } if( in_array( $propName, [ IcalInterface::EXDATE, IcalInterface::RDATE ], true )) { $comp->{$setMethod}( [ $value ], $params ); $getValue = $comp->{$getMethod}(); if( ! empty( $getValue )) { $getValue = reset( $getValue ); } } else { if( in_array( $propName, [ IcalInterface::DTEND, IcalInterface::DUE, IcalInterface::RECURRENCE_ID, ], true ) ) { $comp->setDtstart( $value, $params ); } $comp->{$setMethod}( $value, $params ); $getValue = $comp->{$getMethod}(); } // end else $this->assertSame( ! empty( $value ), $comp->{$isMethod}(), self::getErrMsg( null, $case . '-212', __FUNCTION__, $theComp, $isMethod ) . ', exp ' . empty( $value ) ? Vcalendar::FALSE : Vcalendar::TRUE ); if( $expectedGet->value instanceof DateTime && $getValue instanceof DateTime ) { $this->assertEquals( $expectedGet->value->format( DateTimeFactory::$YmdHis ), $getValue->format( DateTimeFactory::$YmdHis ), self::getErrMsg( null, $case . '-213', __FUNCTION__, $theComp, $getMethod ) ); } // end if else { $this->assertEquals( $expectedGet->value ?? '', $getValue, self::getErrMsg( null, $case . '-214', __FUNCTION__, $theComp, $getMethod ) ); } } // end foreach props... } // end foreach $compsProps... } /** * The test method suffix -1b... , single EXDATE + RDATE, case prefix '-1bx' also multi EXDATE + RDATE * * @param int|string $case * @param array $compsProps * @param mixed $value * @param mixed $params * @param pc $expectedGet * @param string $expectedString * @throws Exception */ public function exdateRdateSpecTest( int | string $case, array $compsProps, mixed $value, mixed $params, Pc $expectedGet, string $expectedString ) : void { $c = new Vcalendar(); $pcInput = false; foreach( $compsProps as $theComp => $props ) { $newMethod = 'new' . $theComp; if( IcalInterface::AVAILABLE === $theComp ) { $comp = $c->newVavailability()->{$newMethod}(); } else { $comp = $c->{$newMethod}(); } foreach( $props as $propName ) { [ $createMethod, $deleteMethod, $getMethod, $isMethod, $setMethod ] = self::getPropMethodnames( $propName ); $this->assertFalse( $comp->{$isMethod}(), self::getErrMsg( null, $case . '-1b2', __FUNCTION__, $theComp, $isMethod ) ); if( $pcInput ) { $comp->{$setMethod}( Pc::factory( $value, $params )); } else { $comp->{$setMethod}( $value, $params ); } $pcInput = ! $pcInput; $this->assertSame( ! empty( $value ), $comp->{$isMethod}(), self::getErrMsg( null, $case . '-1b1', __FUNCTION__, $theComp, $isMethod ) . ', exp ' . empty( $value ) ? Vcalendar::FALSE : Vcalendar::TRUE ); $getValue = $comp->{$getMethod}( null, true ); $getValue->removeParam( IcalInterface::ISLOCALTIME ); $this->assertEquals( $expectedGet->params, $getValue->params, self::getErrMsg( null, $case . '-1b2', __FUNCTION__, $theComp, $isMethod ) ); if( ! empty( $expectedGet->value )) { $expVal = $expectedGet->value; switch( true ) { case $expectedGet->hasParamValue(IcalInterface::DATE ) : $fmt = DateTimeFactory::$Ymd; break; case $getValue->hasParamKey( IcalInterface::ISLOCALTIME ) : $fmt = DateTimeFactory::$YmdHis; break; default : $fmt = DateTimeFactory::$YMDHISe; break; } while( is_array( $expVal ) && ! $expVal instanceof DateTime ) { $expVal = reset( $expVal ); } $expGet = $expVal->format( $fmt ); $getVal = reset( $getValue->value ); while( is_array( $getVal ) && ! $getVal instanceof DateTime ) { // exDate/Rdate $getVal = reset( $getVal ); } $getVal = $getVal->format( $fmt ); } // end if else { $expGet = $expectedGet; $getVal = $getValue; } $this->assertEquals( $expGet, $getVal, self::getErrMsg( null, $case . '-1b3', __FUNCTION__, $theComp, $getMethod ) ); $this->assertEquals( strtoupper( $propName ) . $expectedString, trim( $comp->{$createMethod}() ), self::getErrMsg( null, $case . '-1b4', __FUNCTION__, $theComp, $createMethod ) ); $comp->{$deleteMethod}(); if( IcalInterface::DTSTAMP === $propName ) { $this->assertNotFalse( $comp->{$getMethod}(), self::getErrMsg( '(after delete) ', $case . '-1b5', __FUNCTION__, $theComp, $getMethod ) ); } else { $this->assertFalse( $comp->{$getMethod}(), self::getErrMsg( '(after delete) ', $case . '-1b6', __FUNCTION__, $theComp, $getMethod ) ); } $comp->{$setMethod}( $value, $params ); if( ! empty( $value ) && ! is_array( $value )) { $comp->{$setMethod}( [ $value, $value ], $params ); // set aray of (single) values } } // end foreach } // end foreach $this->parseCalendarTest( $case, $c, $expectedString ); } /** * Testing calendar parse and (-re-)create, case prefix '-3x' * * @param int|string $case * @param Vcalendar $calendar * @param string|null $expectedString * @param mixed|null $theComp * @param string|null $propName * @throws Exception */ public function parseCalendarTest( int | string $case, Vcalendar $calendar, string $expectedString = null, mixed $theComp = null, string $propName = null ) : void { static $xmlStartChars = "\n