Version 5.1 - All the GraphQL #32
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Aviat\AnimeClient\Kitsu;
|
||||
use function Aviat\AnimeClient\friendlyTime;
|
||||
|
||||
?>
|
||||
<main class="details fixed">
|
||||
@ -38,14 +38,14 @@ use Aviat\AnimeClient\Kitsu;
|
||||
<?php if (( ! empty($data['episode_length'])) && $data['episode_count'] !== 1): ?>
|
||||
<tr>
|
||||
<td>Episode Length</td>
|
||||
<td><?= Kitsu::friendlyTime($data['episode_length']) ?></td>
|
||||
<td><?= friendlyTime($data['episode_length']) ?></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($data['total_length'], $data['episode_count']) && $data['total_length'] > 0): ?>
|
||||
<tr>
|
||||
<td>Total Length</td>
|
||||
<td><?= Kitsu::friendlyTime($data['total_length']) ?></td>
|
||||
<td><?= friendlyTime($data['total_length']) ?></td>
|
||||
</tr>
|
||||
<?php endif ?>
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" bootstrap="../tests/bootstrap.php" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd">
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">../src</directory>
|
||||
</include>
|
||||
<report>
|
||||
<clover outputFile="logs/clover.xml"/>
|
||||
<html outputDirectory="../coverage"/>
|
||||
@ -27,4 +24,9 @@
|
||||
<server name="REQUEST_URI" value="/"/>
|
||||
<server name="REQUEST_METHOD" value="GET"/>
|
||||
</php>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">../src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
@ -42,8 +42,8 @@
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"laminas/laminas-diactoros": "^2.5.0",
|
||||
"laminas/laminas-httphandlerrunner": "^2.1.0",
|
||||
"laminas/laminas-diactoros": "^3.0.0",
|
||||
"laminas/laminas-httphandlerrunner": "^2.6.1",
|
||||
"maximebf/consolekit": "^1.0.3",
|
||||
"monolog/monolog": "^3.0.0",
|
||||
"php": ">= 8.1.0",
|
||||
|
@ -14,11 +14,11 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu\Transformer;
|
||||
|
||||
use Aviat\AnimeClient\Kitsu;
|
||||
|
||||
use Aviat\AnimeClient\Types\User;
|
||||
use Aviat\Ion\Transformer\AbstractTransformer;
|
||||
|
||||
use function Aviat\AnimeClient\{formatDate, friendlyTime, getDateDiff};
|
||||
|
||||
/**
|
||||
* Transform user profile data for display
|
||||
*
|
||||
@ -41,8 +41,12 @@ final class UserTransformer extends AbstractTransformer
|
||||
return User::from([
|
||||
'about' => $base['about'] ?? '',
|
||||
'avatar' => $base['avatarImage']['original']['url'] ?? NULL,
|
||||
'birthday' => $base['birthday'] !== NULL ? Kitsu::formatDate($base['birthday']) . ' (' . Kitsu::friendlyTime(Kitsu::getDateDiff($base['birthday']), 'year') . ')' : NULL,
|
||||
'joinDate' => Kitsu::formatDate($base['createdAt']) . ' (' . Kitsu::friendlyTime(Kitsu::getDateDiff($base['createdAt']), 'day') . ' ago)',
|
||||
'birthday' => $base['birthday'] !== NULL
|
||||
? formatDate($base['birthday']) . ' (' .
|
||||
friendlyTime(getDateDiff($base['birthday']), 'year') . ')'
|
||||
: NULL,
|
||||
'joinDate' => formatDate($base['createdAt']) . ' (' .
|
||||
friendlyTime(getDateDiff($base['createdAt']), 'day') . ' ago)',
|
||||
'gender' => $base['gender'],
|
||||
'favorites' => $this->organizeFavorites($favorites),
|
||||
'location' => $base['location'],
|
||||
@ -84,7 +88,7 @@ final class UserTransformer extends AbstractTransformer
|
||||
if (array_key_exists('animeAmountConsumed', $stats))
|
||||
{
|
||||
$animeStats = [
|
||||
'Time spent watching anime:' => Kitsu::friendlyTime($stats['animeAmountConsumed']['time']),
|
||||
'Time spent watching anime:' => friendlyTime($stats['animeAmountConsumed']['time']),
|
||||
'Anime series watched:' => number_format($stats['animeAmountConsumed']['media']),
|
||||
'Anime episodes watched:' => number_format($stats['animeAmountConsumed']['units']),
|
||||
];
|
||||
|
@ -17,6 +17,7 @@ namespace Aviat\AnimeClient;
|
||||
use Amp\Http\Client\{HttpClient, HttpClientBuilder, Request, Response};
|
||||
|
||||
use Aviat\Ion\{ConfigInterface, ImageBuilder};
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\Attributes\CodeCoverageIgnore;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Throwable;
|
||||
@ -26,6 +27,11 @@ use Yosymfony\Toml\{Toml, TomlBuilder};
|
||||
use function Amp\Promise\wait;
|
||||
use function Aviat\Ion\_dir;
|
||||
|
||||
const SECONDS_IN_MINUTE = 60;
|
||||
const MINUTES_IN_HOUR = 60;
|
||||
const MINUTES_IN_DAY = 1440;
|
||||
const MINUTES_IN_YEAR = 525_600;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
//! TOML Functions
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -307,3 +313,87 @@ function renderTemplate(string $path, array $data): string
|
||||
|
||||
return (is_string($rawOutput)) ? $rawOutput : '';
|
||||
}
|
||||
|
||||
function formatDate(string $date): string
|
||||
{
|
||||
$date = new DateTimeImmutable($date);
|
||||
|
||||
return $date->format('F d, Y');
|
||||
}
|
||||
|
||||
function getDateDiff(string $date): int
|
||||
{
|
||||
$now = new DateTimeImmutable();
|
||||
$then = new DateTimeImmutable($date);
|
||||
|
||||
$interval = $now->diff($then, TRUE);
|
||||
|
||||
$years = $interval->y * SECONDS_IN_MINUTE * MINUTES_IN_YEAR;
|
||||
$days = $interval->d * SECONDS_IN_MINUTE * MINUTES_IN_DAY;
|
||||
$hours = $interval->h * SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
|
||||
$minutes = $interval->i * SECONDS_IN_MINUTE;
|
||||
$seconds = $interval->s;
|
||||
|
||||
return $years + $days + $hours + $minutes + $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a time in seconds to a more human-readable format
|
||||
*/
|
||||
function friendlyTime(int $seconds, string $minUnit = 'second'): string
|
||||
{
|
||||
// All the seconds left
|
||||
$remSeconds = $seconds % SECONDS_IN_MINUTE;
|
||||
$minutes = ($seconds - $remSeconds) / SECONDS_IN_MINUTE;
|
||||
|
||||
// Minutes short of a year
|
||||
$years = (int) floor($minutes / MINUTES_IN_YEAR);
|
||||
$minutes %= MINUTES_IN_YEAR;
|
||||
|
||||
// Minutes short of a day
|
||||
$extraMinutes = $minutes % MINUTES_IN_DAY;
|
||||
$days = ($minutes - $extraMinutes) / MINUTES_IN_DAY;
|
||||
|
||||
// Minutes short of an hour
|
||||
$remMinutes = $extraMinutes % MINUTES_IN_HOUR;
|
||||
$hours = ($extraMinutes - $remMinutes) / MINUTES_IN_HOUR;
|
||||
|
||||
$parts = [];
|
||||
|
||||
foreach ([
|
||||
'year' => $years,
|
||||
'day' => $days,
|
||||
'hour' => $hours,
|
||||
'minute' => $remMinutes,
|
||||
'second' => $remSeconds,
|
||||
] as $label => $value)
|
||||
{
|
||||
if ($value === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value > 1)
|
||||
{
|
||||
$label .= 's';
|
||||
}
|
||||
|
||||
$parts[] = "{$value} {$label}";
|
||||
|
||||
if ($label === $minUnit || $label === $minUnit . 's')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$last = array_pop($parts);
|
||||
|
||||
if (empty($parts))
|
||||
{
|
||||
return $last ?? '';
|
||||
}
|
||||
|
||||
return (count($parts) > 1)
|
||||
? implode(', ', $parts) . ", and {$last}"
|
||||
: "{$parts[0]}, {$last}";
|
||||
}
|
||||
|
@ -31,10 +31,6 @@ final class Kitsu
|
||||
public const ANIME_HISTORY_LIST_CACHE_KEY = 'kitsu-anime-history-list';
|
||||
public const MANGA_HISTORY_LIST_CACHE_KEY = 'kitsu-manga-history-list';
|
||||
public const GRAPHQL_ENDPOINT = 'https://kitsu.io/api/graphql';
|
||||
public const SECONDS_IN_MINUTE = 60;
|
||||
public const MINUTES_IN_HOUR = 60;
|
||||
public const MINUTES_IN_DAY = 1440;
|
||||
public const MINUTES_IN_YEAR = 525_600;
|
||||
|
||||
/**
|
||||
* Determine whether an anime is airing, finished airing, or has not yet aired
|
||||
@ -130,29 +126,6 @@ final class Kitsu
|
||||
return MangaPublishingStatus::NOT_YET_PUBLISHED;
|
||||
}
|
||||
|
||||
public static function formatDate(string $date): string
|
||||
{
|
||||
$date = new DateTimeImmutable($date);
|
||||
|
||||
return $date->format('F d, Y');
|
||||
}
|
||||
|
||||
public static function getDateDiff(string $date): int
|
||||
{
|
||||
$now = new DateTimeImmutable();
|
||||
$then = new DateTimeImmutable($date);
|
||||
|
||||
$interval = $now->diff($then, TRUE);
|
||||
|
||||
$years = $interval->y * self::SECONDS_IN_MINUTE * self::MINUTES_IN_YEAR;
|
||||
$days = $interval->d * self::SECONDS_IN_MINUTE * self::MINUTES_IN_DAY;
|
||||
$hours = $interval->h * self::SECONDS_IN_MINUTE * self::MINUTES_IN_HOUR;
|
||||
$minutes = $interval->i * self::SECONDS_IN_MINUTE;
|
||||
$seconds = $interval->s;
|
||||
|
||||
return $years + $days + $hours + $minutes + $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
@ -464,67 +437,6 @@ final class Kitsu
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a time in seconds to a more human-readable format
|
||||
*/
|
||||
public static function friendlyTime(int $seconds, string $minUnit = 'second'): string
|
||||
{
|
||||
// All the seconds left
|
||||
$remSeconds = $seconds % self::SECONDS_IN_MINUTE;
|
||||
$minutes = ($seconds - $remSeconds) / self::SECONDS_IN_MINUTE;
|
||||
|
||||
// Minutes short of a year
|
||||
$years = (int) floor($minutes / self::MINUTES_IN_YEAR);
|
||||
$minutes %= self::MINUTES_IN_YEAR;
|
||||
|
||||
// Minutes short of a day
|
||||
$extraMinutes = $minutes % self::MINUTES_IN_DAY;
|
||||
$days = ($minutes - $extraMinutes) / self::MINUTES_IN_DAY;
|
||||
|
||||
// Minutes short of an hour
|
||||
$remMinutes = $extraMinutes % self::MINUTES_IN_HOUR;
|
||||
$hours = ($extraMinutes - $remMinutes) / self::MINUTES_IN_HOUR;
|
||||
|
||||
$parts = [];
|
||||
|
||||
foreach ([
|
||||
'year' => $years,
|
||||
'day' => $days,
|
||||
'hour' => $hours,
|
||||
'minute' => $remMinutes,
|
||||
'second' => $remSeconds,
|
||||
] as $label => $value)
|
||||
{
|
||||
if ($value === 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value > 1)
|
||||
{
|
||||
$label .= 's';
|
||||
}
|
||||
|
||||
$parts[] = "{$value} {$label}";
|
||||
|
||||
if ($label === $minUnit || $label === $minUnit . 's')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$last = array_pop($parts);
|
||||
|
||||
if (empty($parts))
|
||||
{
|
||||
return $last ?? '';
|
||||
}
|
||||
|
||||
return (count($parts) > 1)
|
||||
? implode(', ', $parts) . ", and {$last}"
|
||||
: "{$parts[0]}, {$last}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an alternate title is unique enough to list
|
||||
*/
|
||||
|
@ -42,6 +42,8 @@ final class APIRequestBuilderTest extends TestCase
|
||||
|
||||
public function testGzipRequest(): void
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$request = $this->builder->newRequest('GET', 'gzip')
|
||||
->getFullRequest();
|
||||
$response = getResponse($request);
|
||||
@ -51,6 +53,8 @@ final class APIRequestBuilderTest extends TestCase
|
||||
|
||||
public function testInvalidRequestMethod(): void
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->builder->newRequest('FOO', 'gzip')
|
||||
->getFullRequest();
|
||||
@ -58,6 +62,8 @@ final class APIRequestBuilderTest extends TestCase
|
||||
|
||||
public function testRequestWithBasicAuth(): void
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$request = $this->builder->newRequest('GET', 'headers')
|
||||
->setBasicAuth('username', 'password')
|
||||
->getFullRequest();
|
||||
@ -70,6 +76,8 @@ final class APIRequestBuilderTest extends TestCase
|
||||
|
||||
public function testRequestWithQueryString(): void
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$query = [
|
||||
'foo' => 'bar',
|
||||
'bar' => [
|
||||
@ -98,6 +106,8 @@ final class APIRequestBuilderTest extends TestCase
|
||||
|
||||
public function testFormValueRequest(): void
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$formValues = [
|
||||
'bar' => 'foo',
|
||||
'foo' => 'bar',
|
||||
@ -115,6 +125,8 @@ final class APIRequestBuilderTest extends TestCase
|
||||
|
||||
public function testFullUrlRequest(): void
|
||||
{
|
||||
$this->markTestSkipped('Need new test API');
|
||||
|
||||
$data = [
|
||||
'foo' => [
|
||||
'bar' => 1,
|
||||
|
@ -38,6 +38,10 @@ final class UserTransformerTest extends AnimeClientTestCase
|
||||
public function testTransform(): void
|
||||
{
|
||||
$actual = (new UserTransformer())->transform($this->beforeTransform);
|
||||
|
||||
// Unset the time value that will change every day, so the test is consistent
|
||||
$actual->joinDate = '';
|
||||
|
||||
$this->assertMatchesSnapshot($actual);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -15,7 +15,8 @@
|
||||
namespace Aviat\AnimeClient\Tests;
|
||||
|
||||
use DateTime;
|
||||
use function Aviat\AnimeClient\{arrayToToml, checkFolderPermissions, clearCache, colNotEmpty, getLocalImg, getResponse, isSequentialArray, tomlToArray};
|
||||
use function Aviat\AnimeClient\{arrayToToml, checkFolderPermissions, clearCache, colNotEmpty, friendlyTime, getLocalImg, getResponse, isSequentialArray, tomlToArray};
|
||||
use const Aviat\AnimeClient\{MINUTES_IN_DAY, MINUTES_IN_HOUR, MINUTES_IN_YEAR, SECONDS_IN_MINUTE};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -128,4 +129,33 @@ final class AnimeClientTest extends AnimeClientTestCase
|
||||
{
|
||||
$this->assertTrue(clearCache($this->container->get('cache')));
|
||||
}
|
||||
|
||||
public static function getFriendlyTime(): array
|
||||
{
|
||||
$SECONDS_IN_DAY = SECONDS_IN_MINUTE * MINUTES_IN_DAY;
|
||||
$SECONDS_IN_HOUR = SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
|
||||
$SECONDS_IN_YEAR = SECONDS_IN_MINUTE * MINUTES_IN_YEAR;
|
||||
|
||||
return [[
|
||||
'seconds' => $SECONDS_IN_YEAR,
|
||||
'expected' => '1 year',
|
||||
], [
|
||||
'seconds' => $SECONDS_IN_HOUR,
|
||||
'expected' => '1 hour',
|
||||
], [
|
||||
'seconds' => (2 * $SECONDS_IN_YEAR) + 30,
|
||||
'expected' => '2 years, 30 seconds',
|
||||
], [
|
||||
'seconds' => (5 * $SECONDS_IN_YEAR) + (3 * $SECONDS_IN_DAY) + (17 * SECONDS_IN_MINUTE),
|
||||
'expected' => '5 years, 3 days, and 17 minutes',
|
||||
]];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('getFriendlyTime')]
|
||||
public function testGetFriendlyTime(int $seconds, string $expected): void
|
||||
{
|
||||
$actual = friendlyTime($seconds);
|
||||
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
}
|
||||
|
@ -98,35 +98,6 @@ final class KitsuTest extends TestCase
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
public static function getFriendlyTime(): array
|
||||
{
|
||||
$SECONDS_IN_DAY = Kitsu::SECONDS_IN_MINUTE * Kitsu::MINUTES_IN_DAY;
|
||||
$SECONDS_IN_HOUR = Kitsu::SECONDS_IN_MINUTE * Kitsu::MINUTES_IN_HOUR;
|
||||
$SECONDS_IN_YEAR = Kitsu::SECONDS_IN_MINUTE * Kitsu::MINUTES_IN_YEAR;
|
||||
|
||||
return [[
|
||||
'seconds' => $SECONDS_IN_YEAR,
|
||||
'expected' => '1 year',
|
||||
], [
|
||||
'seconds' => $SECONDS_IN_HOUR,
|
||||
'expected' => '1 hour',
|
||||
], [
|
||||
'seconds' => (2 * $SECONDS_IN_YEAR) + 30,
|
||||
'expected' => '2 years, 30 seconds',
|
||||
], [
|
||||
'seconds' => (5 * $SECONDS_IN_YEAR) + (3 * $SECONDS_IN_DAY) + (17 * Kitsu::SECONDS_IN_MINUTE),
|
||||
'expected' => '5 years, 3 days, and 17 minutes',
|
||||
]];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('getFriendlyTime')]
|
||||
public function testGetFriendlyTime(int $seconds, string $expected): void
|
||||
{
|
||||
$actual = Kitsu::friendlyTime($seconds);
|
||||
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
public function testFilterLocalizedTitles(): void
|
||||
{
|
||||
$input = [
|
||||
|
Loading…
Reference in New Issue
Block a user