calendar logic

Provide data structure for month, weeks and days.
Provide controller to open month, week and day.

Relates: #7887
This commit is contained in:
Daniel Siepmann 2020-10-22 16:31:54 +02:00
parent 10d6e94c4d
commit 98762332f5
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
23 changed files with 1782 additions and 1 deletions

163
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,163 @@
name: CI
on: [push]
jobs:
check-composer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate composer.json
run: composer validate
php-linting:
runs-on: ubuntu-latest
strategy:
matrix:
php-version:
- 7.3
- 7.4
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
- name: PHP lint
run: "find *.php Classes Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l"
check-dependencies:
runs-on: ubuntu-latest
needs: [check-composer]
steps:
- uses: actions/checkout@v2
- name: Keep composer at 1.x
run: sudo composer selfupdate --1
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest --no-plugins
- name: Missing composer requirements
run: ./vendor/bin/composer-require-checker check
xml-linting:
runs-on: ubuntu-latest
needs: [check-composer]
steps:
- uses: actions/checkout@v2
- name: Install xmllint
run: sudo apt-get install libxml2-utils
- name: Keep composer at 1.x
run: sudo composer selfupdate --1
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: PHPUnit configuration file
run: xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist
- name: PHPCodeSniffer configuration file
run: xmllint --schema vendor/squizlabs/php_codesniffer/phpcs.xsd --noout phpcs.xml.dist
- name: Fetch schema for xliff
run: wget https://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --output-document=.Build/xliff-core-1.2-strict.xsd
- name: TYPO3 language files
run: xmllint --schema .Build/xliff-core-1.2-strict.xsd --noout $(find Resources -name '*.xlf')
coding-guideline:
runs-on: ubuntu-latest
needs:
- check-dependencies
- xml-linting
steps:
- uses: actions/checkout@v2
- name: Keep composer at 1.x
run: sudo composer selfupdate --1
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Coding Guideline
run: ./vendor/bin/phpcs
tests:
runs-on: ubuntu-latest
needs:
- check-dependencies
- xml-linting
strategy:
matrix:
php-version:
- 7.3
- 7.4
steps:
- uses: actions/checkout@v2
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
- name: Keep composer at 1.x
run: sudo composer selfupdate --1
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: PHPUnit Tests
run: ./vendor/bin/phpunit --testdox

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/.Build/
/vendor/
/composer.lock

View file

@ -0,0 +1,115 @@
<?php
namespace WerkraumMedia\Calendar\Controller\Frontend;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Extbase\Annotation as Extbase;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
use WerkraumMedia\Calendar\Domain\Model\Day;
use WerkraumMedia\Calendar\Domain\Model\Month;
use WerkraumMedia\Calendar\Domain\Model\Week;
class CalendarController extends ActionController
{
public function initializeMonthAction()
{
if ($this->request->hasArgument('month') === false) {
$this->request->setArguments([
'month' => [
'month' => date('m'),
'year' => date('Y'),
],
]);
}
$this->arguments->getArgument('month')
->getPropertyMappingConfiguration()
->allowAllProperties();
}
/**
* @Extbase\IgnoreValidation("month")
*/
public function monthAction(Month $month)
{
$this->view->assignMultiple([
'month' => $month,
]);
}
public function initializeWeekAction()
{
if ($this->request->hasArgument('week') === false) {
$this->request->setArguments([
'week' => [
'week' => date('W'),
'year' => date('Y'),
],
]);
}
$this->arguments->getArgument('week')
->getPropertyMappingConfiguration()
->allowAllProperties();
}
/**
* @Extbase\IgnoreValidation("week")
*/
public function weekAction(Week $week)
{
$this->view->assignMultiple([
'week' => $week,
]);
}
public function initializeDayAction()
{
if ($this->request->hasArgument('day') === false) {
$this->request->setArguments([
'day' => new \DateTimeImmutable(),
]);
}
$propertyMappingConfiguration = $this->arguments->getArgument('day')
->getPropertyMappingConfiguration();
$propertyMappingConfiguration->allowAllProperties();
$propertyMappingConfiguration
->forProperty('day')
->setTypeConverterOption(
DateTimeConverter::class,
DateTimeConverter::CONFIGURATION_DATE_FORMAT,
'Y-m-d'
);
}
/**
* @Extbase\IgnoreValidation("day")
*/
public function dayAction(Day $day)
{
$this->view->assignMultiple([
'day' => $day,
]);
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace WerkraumMedia\Calendar\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Day
{
/**
* @var \DateTimeImmutable
*/
private $day;
/**
* @var mixed
*/
private $foreignData;
/**
* @var bool
*/
private $initialized = false;
public function __construct(
\DateTime $day
) {
$this->periods = new ObjectStorage();
$this->day = \DateTimeImmutable::createFromMutable($day)->modify('midnight');
}
public function isActive(): bool
{
$foreignData = $this->getForeignData();
if ($foreignData instanceof IsDayActive) {
return $foreignData->isActive($this->getDateTimeInstance());
}
return false;
}
public function getForeignData()
{
$this->initializeForeignData();
return $this->foreignData;
}
public function getDateTimeInstance(): \DateTimeImmutable
{
return $this->day;
}
public function getAsUrlArgument(): array
{
return [
'day' => $this->day->format('Y-m-d'),
];
}
private function initializeForeignData(): void
{
if ($this->initialized) {
return;
}
$this->foreignData = GeneralUtility::makeInstance(ForeignDataFactory::class)
->getData($this);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace WerkraumMedia\Calendar\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
interface ForeignDataFactory
{
/**
*
*/
public function getData(Day $day);
}

View file

@ -0,0 +1,27 @@
<?php
namespace WerkraumMedia\Calendar\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
interface IsDayActive
{
public function isActive(\DateTimeImmutable $dateTime): bool;
}

View file

@ -0,0 +1,129 @@
<?php
namespace WerkraumMedia\Calendar\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Month
{
/**
* @var int
*/
private $month;
/**
* @var int
*/
private $year;
/**
* @var Week[]
*/
private $weeks = [];
public function __construct(
int $month,
int $year
) {
$this->month = $month;
$this->year = $year;
}
public function isActive(): bool
{
foreach ($this->getWeeks() as $week) {
if ($week->isActive()) {
return true;
}
}
return false;
}
public function getWeeks(): array
{
if ($this->weeks !== []) {
return $this->weeks;
}
$month = $this->getDateTimeInstance();
$lastDay = $month->modify('last day of this month')->modify('sunday this week');
$currentDay = $month->modify('monday this week');
while ($currentDay <= $lastDay) {
$this->weeks[] = new Week(
(int) $currentDay->format('W'),
(int) $currentDay->format('Y')
);
$currentDay = $currentDay->modify('+7 days');
}
return $this->weeks;
}
public function getPreviousMonth(): Month
{
$previousMonth = $this->month - 1;
$previousYear = $this->year;
if ($previousMonth <= 0) {
$previousMonth = 12;
--$previousYear;
}
return new self(
$previousMonth,
$previousYear
);
}
public function getNextMonth(): Month
{
$nextMonth = $this->month + 1;
$nextYear = $this->year;
if ($nextMonth > 12) {
$nextMonth = 1;
++$nextYear;
}
return new self(
$nextMonth,
$nextYear
);
}
public function getAsUrlArgument(): array
{
return [
'month' => $this->month,
'year' => $this->year,
];
}
public function getDateTimeInstance(): \DateTimeImmutable
{
return new \DateTimeImmutable($this->year . '-' . $this->month . '-01');
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace WerkraumMedia\Calendar\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
class NullDataFactory implements ForeignDataFactory
{
public function getData(Day $day)
{
return null;
}
}

View file

@ -0,0 +1,118 @@
<?php
namespace WerkraumMedia\Calendar\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
class Week
{
/**
* @var int
*/
private $week;
/**
* @var int
*/
private $year;
/**
* @var Day[]
*/
private $days = [];
public function __construct(
int $week,
int $year
) {
$this->week = $week;
$this->year = $year;
}
public function isActive(): bool
{
foreach ($this->getDays() as $day) {
if ($day->isActive()) {
return true;
}
}
return false;
}
public function getDays(): array
{
if ($this->days !== []) {
return $this->days;
}
$currentDay = $this->getWeek()->modify('monday this week');
$endOfWeek = $currentDay->modify('sunday this week');
while ($currentDay <= $endOfWeek) {
$this->days[] = new Day(\DateTime::createFromImmutable($currentDay));
$currentDay = $currentDay->modify('+1 day');
}
return $this->days;
}
public function getPreviousWeek(): Week
{
$newDay = $this->getWeek()->modify('-1 week');
return new self(
(int) $newDay->format('W'),
(int) $newDay->format('Y')
);
}
public function getNextWeek(): Week
{
$newDay = $this->getWeek()->modify('+1 week');
return new self(
(int) $newDay->format('W'),
(int) $newDay->format('Y')
);
}
public function getDateTimeInstance(): \DateTimeImmutable
{
return $this->getWeek();
}
public function getAsUrlArgument(): array
{
return [
'week' => $this->week,
'year' => $this->year,
];
}
private function getWeek(): \DateTimeImmutable
{
$week = new \DateTimeImmutable();
$week = $week->setISODate($this->year, $this->week);
$week = $week->modify('thursday');
return $week;
}
}

View file

@ -0,0 +1,8 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false
WerkraumMedia\Calendar\:
resource: '../Classes/*'

View file

@ -0,0 +1,8 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">
<h2>{day.dateTimeInstance -> f:format.date(format: 'd.m.Y')}</h2>
TODO: Show periods
Maybe remove and instead link to filtered list
</html>

View file

@ -0,0 +1,42 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">
<h2>{month.dateTimeInstance -> f:format.date(format: '%B %Y')}</h2>
<f:link.action action="month" controller="Frontend\Calendar" arguments="{month: month.previousMonth}">
Prev Month
</f:link.action>
<f:link.action action="month" controller="Frontend\Calendar" arguments="{month: month.nextMonth}">
Next Month
</f:link.action>
<table>
<tr>
<th>KW</th>
<th>Mo</th>
<th>Di</th>
<th>Mi</th>
<th>Do</th>
<th>Fr</th>
<th>Sa</th>
<th>So</th>
</tr>
<f:for each="{month.weeks}" as="week">
<tr class="{f:if(condition: week.active, then: 'active', else: 'inactive')}">
<td>
<f:link.action action="week" controller="Frontend\Calendar" arguments="{week: week.asUrlArgument}">
{week.dateTimeInstance -> f:format.date(format: '%V')}
</f:link.action>
</td>
<f:for each="{week.days}" as="day">
<td class="{f:if(condition: day.active, then: 'active', else: 'inactive')}">
<f:link.action action="day" controller="Frontend\Calendar" arguments="{day: day.asUrlArgument}">
{day.dateTimeInstance -> f:format.date(format: 'd')}
</f:link.action>
</td>
</f:for>
</tr>
</f:for>
</table>
</html>

View file

@ -0,0 +1,33 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">
<h2>{week.dateTimeInstance -> f:format.date(format: '%V %Y')}</h2>
<f:link.action action="week" controller="Frontend\Calendar" arguments="{week: week.previousWeek}">
Prev Week
</f:link.action>
<f:link.action action="week" controller="Frontend\Calendar" arguments="{week: week.nextWeek}">
Next Week
</f:link.action>
<table>
<tr>
<th>Mo</th>
<th>Di</th>
<th>Mi</th>
<th>Do</th>
<th>Fr</th>
<th>Sa</th>
<th>So</th>
</tr>
<tr>
<f:for each="{week.days}" as="day">
<td class="{f:if(condition: day.active, then: 'active', else: 'inactive')}">
<f:link.action action="day" controller="Frontend\Calendar" arguments="{day: day.asUrlArgument}">
{day.dateTimeInstance -> f:format.date(format: 'd')}
</f:link.action>
</td>
</f:for>
</tr>
</table>
</html>

View file

@ -0,0 +1,33 @@
<?php
namespace WerkraumMedia\Calendar\Tests;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
trait ForcePropertyTrait
{
protected function forceProperty($subject, string $name, $value)
{
$objectReflection = new \ReflectionObject($subject);
$property = $objectReflection->getProperty($name);
$property->setAccessible(true);
$property->setValue($subject, $value);
}
}

View file

@ -0,0 +1,275 @@
<?php
namespace WerkraumMedia\Calendar\Tests\Unit\Controller\Frontend;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use PHPUnit\Framework\TestCase;
use Prophecy\Argument as ProphetArgument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use TYPO3\CMS\Extbase\Mvc\Controller\Argument;
use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extbase\Mvc\Web\Request;
use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
use WerkraumMedia\Calendar\Controller\Frontend\CalendarController;
use WerkraumMedia\Calendar\Domain\Model\Day;
use WerkraumMedia\Calendar\Domain\Model\Month;
use WerkraumMedia\Calendar\Domain\Model\Week;
use WerkraumMedia\Calendar\Tests\ForcePropertyTrait;
/**
* @covers WerkraumMedia\Calendar\Controller\Frontend\CalendarController
* @testdox The calendar controller
*/
class CalendarControllerTest extends TestCase
{
use ProphecyTrait;
use ForcePropertyTrait;
/**
* @test
*/
public function setsCurrentMonthAsDefaultArgument(): void
{
$subject = new CalendarController();
$arguments = $this->allowsMappingOfAllPropertiesForArgument('month')['arguments'];
$request = $this->prophesize(Request::class);
$request->hasArgument('month')->willReturn(false);
$request->setArguments([
'month' => [
'month' => date('m'),
'year' => date('Y'),
],
])->shouldBeCalled();
$this->forceProperty($subject, 'request', $request->reveal());
$this->forceProperty($subject, 'arguments', $arguments->reveal());
$subject->initializeMonthAction();
$request->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function allowsMonthToBeMapped(): void
{
$subject = new CalendarController();
$arguments = $this->allowsMappingOfAllPropertiesForArgument('month')['arguments'];
$request = $this->prophesize(Request::class);
$request->hasArgument('month')->willReturn(true);
$this->forceProperty($subject, 'request', $request->reveal());
$this->forceProperty($subject, 'arguments', $arguments->reveal());
$subject->initializeMonthAction();
$arguments->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function addsMonthToView(): void
{
$subject = new CalendarController();
$month = $this->prophesize(Month::class);
$view = $this->prophesize(ViewInterface::class);
$view->assignMultiple([
'month' => $month->reveal(),
])->shouldBeCalled();
$this->forceProperty($subject, 'view', $view->reveal());
$subject->monthAction($month->reveal());
$view->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function setsCurrentWeekAsDefaultArgument(): void
{
$subject = new CalendarController();
$arguments = $this->allowsMappingOfAllPropertiesForArgument('week')['arguments'];
$request = $this->prophesize(Request::class);
$request->hasArgument('week')->willReturn(false);
$request->setArguments([
'week' => [
'week' => date('W'),
'year' => date('Y'),
],
])->shouldBeCalled();
$this->forceProperty($subject, 'request', $request->reveal());
$this->forceProperty($subject, 'arguments', $arguments->reveal());
$subject->initializeWeekAction();
$request->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function allowsWeekToBeMapped(): void
{
$subject = new CalendarController();
$arguments = $this->allowsMappingOfAllPropertiesForArgument('week')['arguments'];
$request = $this->prophesize(Request::class);
$request->hasArgument('week')->willReturn(true);
$this->forceProperty($subject, 'request', $request->reveal());
$this->forceProperty($subject, 'arguments', $arguments->reveal());
$subject->initializeWeekAction();
$arguments->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function addsWeekToView(): void
{
$subject = new CalendarController();
$week = $this->prophesize(Week::class);
$view = $this->prophesize(ViewInterface::class);
$view->assignMultiple([
'week' => $week->reveal(),
])->shouldBeCalled();
$this->forceProperty($subject, 'view', $view->reveal());
$subject->weekAction($week->reveal());
$view->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function setsCurrentDayAsDefaultArgument(): void
{
$subject = new CalendarController();
$prophecies = $this->allowsMappingOfAllPropertiesForArgument('day');
$propertyConfiguration = $prophecies['propertyMappingConfiguration'];
$arguments = $prophecies['arguments'];
$request = $this->prophesize(Request::class);
$request->hasArgument('day')->willReturn(false);
$request->setArguments(ProphetArgument::that(function (array $arguments) {
return count($arguments) === 1
&& isset($arguments['day'])
&& $arguments['day'] instanceof \DateTimeImmutable
&& $arguments['day']->format('Y-m-d') === date('Y-m-d')
;
}))->shouldBeCalled();
$configuration = $this->prophesize(PropertyMappingConfiguration::class);
$configuration->setTypeConverterOption(
'',
'',
'Y-m-d'
);
$propertyConfiguration->forProperty('day')->willReturn($configuration);
$this->forceProperty($subject, 'request', $request->reveal());
$this->forceProperty($subject, 'arguments', $arguments->reveal());
$subject->initializeDayAction();
$request->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function configuredMappingForDay(): void
{
$subject = new CalendarController();
$prophecies = $this->allowsMappingOfAllPropertiesForArgument('day');
$propertyConfiguration = $prophecies['propertyMappingConfiguration'];
$arguments = $prophecies['arguments'];
$request = $this->prophesize(Request::class);
$request->hasArgument('day')->willReturn(true);
$configuration = $this->prophesize(PropertyMappingConfiguration::class);
$configuration->setTypeConverterOption(
'',
'',
'Y-m-d'
);
$propertyConfiguration->forProperty('day')->willReturn($configuration);
$this->forceProperty($subject, 'request', $request->reveal());
$this->forceProperty($subject, 'arguments', $arguments->reveal());
$subject->initializeDayAction();
$arguments->checkProphecyMethodsPredictions();
}
/**
* @test
*/
public function addsDayToView(): void
{
$subject = new CalendarController();
$day = $this->prophesize(Day::class);
$view = $this->prophesize(ViewInterface::class);
$view->assignMultiple([
'day' => $day->reveal(),
])->shouldBeCalled();
$this->forceProperty($subject, 'view', $view->reveal());
$subject->dayAction($day->reveal());
$view->checkProphecyMethodsPredictions();
}
private function allowsMappingOfAllPropertiesForArgument(string $argumentName): array
{
$propertyMappingConfiguration = $this->prophesize(PropertyMappingConfiguration::class);
$propertyMappingConfiguration->allowAllProperties()->shouldBeCalled();
$argument = $this->prophesize(Argument::class);
$argument->getPropertyMappingConfiguration()->willReturn($propertyMappingConfiguration);
$arguments = $this->prophesize(Arguments::class);
$arguments->getArgument($argumentName)->willReturn($argument->reveal());
return [
'propertyMappingConfiguration' => $propertyMappingConfiguration,
'argument' => $argument,
'arguments' => $arguments,
];
}
}

View file

@ -0,0 +1,144 @@
<?php
namespace WerkraumMedia\Calendar\Tests\Unit\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use WerkraumMedia\Calendar\Domain\Model\Day;
use WerkraumMedia\Calendar\Domain\Model\ForeignDataFactory;
use WerkraumMedia\Calendar\Domain\Model\IsDayActive;
use WerkraumMedia\Calendar\Domain\Model\NullDataFactory;
use WerkraumMedia\Calendar\Tests\ForcePropertyTrait;
/**
* @covers WerkraumMedia\Calendar\Domain\Model\Day
* @testdox A day
*/
class DayTest extends TestCase
{
use ProphecyTrait;
use ForcePropertyTrait;
public function tearDown(): void
{
GeneralUtility::purgeInstances();
parent::tearDown();
}
/**
* @test
*/
public function canBeCreated(): void
{
$subject = new Day(
new \DateTime()
);
self::assertInstanceOf(Day::class, $subject);
}
/**
* @test
*/
public function providesDateTimeInstance(): void
{
$dateTimeInstance = new \DateTime();
$subject = new Day(
$dateTimeInstance
);
self::assertInstanceOf(\DateTimeImmutable::class, $subject->getDateTimeInstance());
self::assertSame($dateTimeInstance->format('U'), $subject->getDateTimeInstance()->format('U'));
}
/**
* @test
*/
public function providedDateTimeInstanceHasMidnight(): void
{
$dateTimeInstance = new \DateTime();
$subject = new Day(
$dateTimeInstance
);
self::assertSame('00:00:00', $subject->getDateTimeInstance()->format('H:i:s'));
}
/**
* @test
*/
public function providesItselfAsUrlArgument(): void
{
$subject = new Day(new \DateTime('2020-10-19'));
self::assertSame(['day' => '2020-10-19'], $subject->getAsUrlArgument());
}
/**
* @test
*/
public function isNotActiveIfNoForeignDataWithInterfaceExists(): void
{
$subject = new Day(new \DateTime('2020-10-19'));
$this->forceProperty($subject, 'initialized', true);
self::assertFalse($subject->isActive());
}
/**
* @test
*/
public function isNotActiveIfForeignDataIsNotActive(): void
{
$subject = new Day(new \DateTime('2020-10-19'));
$foreignData = $this->prophesize(IsDayActive::class);
$foreignData->isActive(Argument::any())->willReturn(false);
$this->forceProperty($subject, 'initialized', true);
$this->forceProperty($subject, 'foreignData', $foreignData->reveal());
self::assertFalse($subject->isActive());
}
/**
* @test
*/
public function initializesForeignDataViaFactory(): void
{
$subject = new Day(new \DateTime('2020-10-19'));
$foreignData = $this->prophesize(IsDayActive::class);
$foreignData->isActive(Argument::any())->willReturn(true);
$factory = $this->prophesize(ForeignDataFactory::class);
$factory->getData($subject)->willReturn($foreignData->reveal());
GeneralUtility::addInstance(ForeignDataFactory::class, $factory->reveal());
self::assertTrue($subject->isActive());
}
}

View file

@ -0,0 +1,183 @@
<?php
namespace WerkraumMedia\Calendar\Tests\Unit\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use WerkraumMedia\Calendar\Domain\Model\Month;
use WerkraumMedia\Calendar\Domain\Model\Week;
use WerkraumMedia\Calendar\Tests\ForcePropertyTrait;
/**
* @covers WerkraumMedia\Calendar\Domain\Model\Month
* @testdox A month
*/
class MonthTest extends TestCase
{
use ProphecyTrait;
use ForcePropertyTrait;
/**
* @test
*/
public function canBeCreated(): void
{
$subject = new Month(1, 2020);
self::assertInstanceOf(Month::class, $subject);
}
/**
* @test
*/
public function returnsPreviousMonthForSameYear(): void
{
$subject = new Month(2, 2020);
self::assertSame('01', $subject->getPreviousMonth()->getDateTimeInstance()->format('m'));
self::assertSame('2020', $subject->getPreviousMonth()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsPreviousMonthForPreviousYear(): void
{
$subject = new Month(1, 2020);
self::assertSame('12', $subject->getPreviousMonth()->getDateTimeInstance()->format('m'));
self::assertSame('2019', $subject->getPreviousMonth()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsNextMonthForSameYear(): void
{
$subject = new Month(1, 2020);
self::assertSame('02', $subject->getNextMonth()->getDateTimeInstance()->format('m'));
self::assertSame('2020', $subject->getNextMonth()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsNextMonthForNextYear(): void
{
$subject = new Month(12, 2020);
self::assertSame('01', $subject->getNextMonth()->getDateTimeInstance()->format('m'));
self::assertSame('2021', $subject->getNextMonth()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsFiveWeeksForDecember2020(): void
{
$subject = new Month(12, 2020);
$weeks = $subject->getWeeks();
self::assertCount(5, $weeks);
self::assertSame('2020-11-30', $weeks[0]->getDays()[0]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2021-01-03', $weeks[4]->getDays()[6]->getDateTimeInstance()->format('Y-m-d'));
}
/**
* @test
*/
public function returnsSixWeeksForNovember2020(): void
{
$subject = new Month(11, 2020);
$weeks = $subject->getWeeks();
self::assertCount(6, $weeks);
self::assertSame('2020-10-26', $weeks[0]->getDays()[0]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-12-06', $weeks[5]->getDays()[6]->getDateTimeInstance()->format('Y-m-d'));
}
/**
* @test
*/
public function returnsSameWeeksOnSecondCall(): void
{
$subject = new Month(11, 2020);
self::assertSame($subject->getWeeks(), $subject->getWeeks());
}
/**
* @test
*/
public function providesDateTimeInstance(): void
{
$subject = new Month(02, 2018);
self::assertSame('2018-02-01', $subject->getDateTimeInstance()->format('Y-m-d'));
}
/**
* @test
*/
public function returnsAsUrlArguments(): void
{
$subject = new Month(02, 2018);
self::assertSame([
'month' => 2,
'year' => 2018,
], $subject->getAsUrlArgument());
}
/**
* @test
*/
public function returnsNotActiveIfAllWeeksAreInactive(): void
{
$subject = new Month(02, 2018);
$week = $this->prophesize(Week::class);
$week->isActive()->willReturn(false);
$weeks = [$week->reveal()];
$this->forceProperty($subject, 'weeks', $weeks);
self::assertFalse($subject->isActive());
}
/**
* @test
*/
public function returnsActiveIfASingleWeekIsActive(): void
{
$subject = new Month(02, 2018);
$week = $this->prophesize(Week::class);
$week->isActive()->willReturn(true);
$week2 = $this->prophesize(Week::class);
$week2->isActive()->willReturn(false);
$weeks = [$week->reveal(), $week2->reveal()];
$this->forceProperty($subject, 'weeks', $weeks);
self::assertTrue($subject->isActive());
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace WerkraumMedia\Calendar\Tests\Unit\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use Prophecy\PhpUnit\ProphecyTrait;
use WerkraumMedia\Calendar\Domain\Model\Day;
use WerkraumMedia\Calendar\Domain\Model\NullDataFactory;
use PHPUnit\Framework\TestCase;
/**
* @covers WerkraumMedia\Calendar\Domain\Model\NullDataFactory
*/
class NullDataFactoryTest extends TestCase
{
use ProphecyTrait;
/**
* @test
*/
public function returnsNull(): void
{
$subject = new NullDataFactory();
$day = $this->prophesize(Day::class);
self::assertNull($subject->getData($day->reveal()));
}
}

View file

@ -0,0 +1,235 @@
<?php
namespace WerkraumMedia\Calendar\Tests\Unit\Domain\Model;
/*
* Copyright (C) 2020 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use WerkraumMedia\Calendar\Domain\Model\Day;
use WerkraumMedia\Calendar\Domain\Model\Week;
use WerkraumMedia\Calendar\Tests\ForcePropertyTrait;
/**
* @covers WerkraumMedia\Calendar\Domain\Model\Week
* @testdox A week
*/
class WeekTest extends TestCase
{
use ProphecyTrait;
use ForcePropertyTrait;
/**
* @test
*/
public function canBeCreated(): void
{
$subject = new Week(1, 2020);
self::assertInstanceOf(Week::class, $subject);
}
/**
* @test
*/
public function returnsSevenDays(): void
{
$subject = new Week(1, 2020);
self::assertCount(7, $subject->getDays());
}
/**
* @test
*/
public function returnsSameDaysOnSecondCall(): void
{
$subject = new Week(1, 2020);
self::assertSame($subject->getDays(), $subject->getDays());
}
/**
* @test
*/
public function returnsDaysForWeek1(): void
{
$subject = new Week(1, 2020);
$days = $subject->getDays();
self::assertSame('2019-12-30', $days[0]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2019-12-31', $days[1]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-01-01', $days[2]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-01-02', $days[3]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-01-03', $days[4]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-01-04', $days[5]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-01-05', $days[6]->getDateTimeInstance()->format('Y-m-d'));
}
/**
* @test
*/
public function returnsDaysForWeek53(): void
{
$subject = new Week(53, 2020);
$days = $subject->getDays();
self::assertSame('2020-12-28', $days[0]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-12-29', $days[1]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-12-30', $days[2]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2020-12-31', $days[3]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2021-01-01', $days[4]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2021-01-02', $days[5]->getDateTimeInstance()->format('Y-m-d'));
self::assertSame('2021-01-03', $days[6]->getDateTimeInstance()->format('Y-m-d'));
}
/**
* @test
*/
public function returnsPreviousWeekForSameYear(): void
{
$subject = new Week(2, 2020);
self::assertSame('01', $subject->getPreviousWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2020', $subject->getPreviousWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsPreviousWeekForYearSwitchFrom2019To2018(): void
{
$subject = new Week(1, 2019);
self::assertSame('52', $subject->getPreviousWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2018', $subject->getPreviousWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsPreviousWeekForYearSwitchFrom2018To2017(): void
{
$subject = new Week(1, 2018);
self::assertSame('52', $subject->getPreviousWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2017', $subject->getPreviousWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsPreviousWeekForPreviousYear(): void
{
$subject = new Week(1, 2021);
self::assertSame('53', $subject->getPreviousWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2020', $subject->getPreviousWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsNextWeekForSameYear(): void
{
$subject = new Week(1, 2020);
self::assertSame('02', $subject->getNextWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2020', $subject->getNextWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsNextWeekForNextYear(): void
{
$subject = new Week(53, 2020);
self::assertSame('01', $subject->getNextWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2021', $subject->getNextWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function returnsNextWeekForNextYearFrom2018To2019(): void
{
$subject = new Week(52, 2018);
self::assertSame('01', $subject->getNextWeek()->getDateTimeInstance()->format('W'));
self::assertSame('2019', $subject->getNextWeek()->getDateTimeInstance()->format('Y'));
}
/**
* @test
*/
public function providesDateTimeInstance(): void
{
$subject = new Week(52, 2018);
self::assertSame('52', $subject->getDateTimeInstance()->format('W'));
self::assertSame('2018-12-27 Thursday', $subject->getDateTimeInstance()->format('Y-m-d l'));
}
/**
* @test
*/
public function providesItselfAsUrlArgument(): void
{
$subject = new Week(52, 2018);
self::assertSame([
'week' => 52,
'year' => 2018,
], $subject->getAsUrlArgument());
}
/**
* @test
*/
public function returnsNotActiveIfAllDaysAreInactive(): void
{
$subject = new Week(02, 2018);
$day = $this->prophesize(Day::class);
$day->isActive()->willReturn(false);
$days = [$day->reveal()];
$this->forceProperty($subject, 'days', $days);
self::assertFalse($subject->isActive());
}
/**
* @test
*/
public function returnsActiveIfASingleDayActive(): void
{
$subject = new Week(02, 2018);
$day = $this->prophesize(Day::class);
$day->isActive()->willReturn(true);
$day2 = $this->prophesize(Day::class);
$day2->isActive()->willReturn(false);
$days = [$day->reveal(), $day2->reveal()];
$this->forceProperty($subject, 'days', $days);
self::assertTrue($subject->isActive());
}
}

View file

@ -13,6 +13,7 @@
"sort-packages": true
},
"require": {
"php": "^7.3.0 || ^7.4.0",
"typo3/cms-core": "^10.4",
"typo3/cms-extbase": "^10.4"
},
@ -28,8 +29,10 @@
},
"require-dev": {
"jangregor/phpstan-prophecy": "^0.6.2",
"maglnet/composer-require-checker": "^2.1",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.4"
"phpunit/phpunit": "^9.4",
"squizlabs/php_codesniffer": "^3.5"
},
"extra": {
"typo3/cms": {

21
ext_emconf.php Normal file
View file

@ -0,0 +1,21 @@
<?php
$EM_CONF['calendar'] = [
'title' => 'Calendar',
'description' => 'API for extensions to create calendar',
'category' => 'misc',
'author' => 'Daniel Siepmann',
'author_email' => 'coding@daniel-siepmann.de',
'author_company' => 'Codappix GmbH',
'state' => 'alpha',
'uploadfolder' => 0,
'clearCacheOnLoad' => 0,
'version' => '1.0.0',
'constraints' => [
'depends' => [
'typo3' => '*',
],
'conflicts' => [],
'suggests' => [],
],
];

19
phpcs.xml.dist Normal file
View file

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<ruleset name="project">
<description>This project coding standard</description>
<file>Classes/</file>
<file>Tests/</file>
<!-- Set default settings -->
<arg value="sp"/>
<arg name="colors"/>
<arg name="encoding" value="utf-8" />
<arg name="extensions" value="php" />
<!-- Base rules -->
<rule ref="PSR12" />
<rule ref="Generic.Files.LineLength.TooLong">
<exclude-pattern>/Tests/*</exclude-pattern>
</rule>
</ruleset>

30
phpunit.xml.dist Normal file
View file

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
verbose="false"
>
<testsuites>
<testsuite name="unit">
<directory>Tests/Unit/</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">Classes</directory>
</include>
</coverage>
</phpunit>