~thirdplace/components

5cb014a829dacfc059cc954e8ad195d3e8ad7d75 — Dag a month ago 1387565
fix: introduce phpunit
10 files changed, 96 insertions(+), 228 deletions(-)

M .gitignore
D bin/tunit
M composer.json
R tests/{cache.php => CacheTest.php}
A tests/ContainerTest.php
A tests/RouterTest.php
D tests/container.php
D tests/http.php
D tests/router.php
D tests/url.php
M .gitignore => .gitignore +2 -0
@@ 1,1 1,3 @@
composer.lock
vendor
.idea

D bin/tunit => bin/tunit +0 -114
@@ 1,114 0,0 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);

namespace Thirdplace;

function assertNull($value, string $message = null)
{
    if ($value !== null) {
        fail(null, $value, $message);
    }
}

function assertTrue($value, string $message = null)
{
    if ($value !== true) {
        fail(true, $value, $message);
    }
}

function assertFalse($value, string $message = null)
{
    if ($value !== false) {
        fail(false, $value, $message);
    }
}

function assertEquals($expected, $actual, string $message = null)
{
    if ($expected != $actual) {
        fail($expected, $actual, $message);
    }
}

function assertSame($expected, $actual, string $message = null)
{
    if ($expected !== $actual) {
        fail($expected, $actual, $message);
    }
}

function expectException(\Closure $fn, string $expected = \Exception::class, string $message = null)
{
    try {
        $fn();
        fail($expected, '', $message);
    } catch (\Exception $e) {
        if (! $e instanceof $expected) {
            fail($expected, get_class($e), $message);
        }
    }
}

function fail($expected, $actual, string $message = null)
{
    $stackFrame = debug_backtrace()[1];
    printf("\nFAIL at %s line %s\n", $stackFrame['file'], $stackFrame['line']);
    if ($message) {
        printf("Message : %s\n", $message);
    }
    printf("Expected: %s\n", toString($expected));
    printf("Actual:   %s\n", toString($actual));
}

function toString($value)
{
    if ($value === null) {
        return '[null]';
    }
    if (is_bool($value)) {
        return $value ? '[true]' : '[false]';
    }
    if (is_string($value) || method_exists($value, '__toString')) {
        return "'$value'";
    }
    return print_r($value, true);
}

function main($argc, $argv): void
{
    set_error_handler(function ($code, $message, $file, $line) {
        throw new \ErrorException($message, 0, $code, $file, $line);
    });

    $base = $argv[1] ?? './';

    if (!is_dir($base)) {
        exit(sprintf("Folder not found: %s\n", $base));
    }

    $autoloader = "$base/vendor/autoload.php";
    if (is_file($autoloader)) {
        require $autoloader;
    }

    $testsFolder = "$base/tests";
    if (!is_dir($testsFolder)) {
        exit(sprintf("Tests folder not found: %s\n", $testsFolder));
    }

    $tests = glob($testsFolder . '/*.php');
    if (!$tests) {
        exit("Tests folder is empty\n");
    }

    foreach ($tests as $test) {
        require $test;
        print '.';
    }

    print "\n";
}

main($argc, $argv);

M composer.json => composer.json +2 -5
@@ 35,9 35,6 @@
            "./src/Url.php"
        ]
    },
    "bin": [
        "./bin/tunit"
    ],
    "require": {
        "php" : ">=7.4",
        "ext-json": "*",


@@ 47,7 44,7 @@
        "ext-libxml": "*",
        "ext-simplexml": "*"
    },
    "scripts": {
        "test": "./bin/tunit"
    "require-dev": {
        "phpunit/phpunit": "^11"
    }
}

R tests/cache.php => tests/CacheTest.php +31 -28
@@ 1,35 1,38 @@
<?php

declare(strict_types=1);

namespace Thirdplace;

function test_cache(Cache $cache)
{
    assertNull($cache->get('foo'));
    assertSame('default', $cache->get('foo', 'default'));

    $cache->set('foo', 'bar');
    assertSame('bar', $cache->get('foo'));
    assertSame('bar', $cache->get('foo', 'default'));

    $cache->set('is_cool');
    assertTrue($cache->get('is_cool'));

    $cache->set('is_bro', false);
    assertFalse($cache->get('is_bro'));
use PHPUnit\Framework\TestCase;

    $cache->delete('foo');
    assertNull($cache->get('foo'));

    $cache->clear();
    assertNull($cache->get('is_bro'));
}

$caches = [
    new ArrayCache,
    new PdoCache(new \PDO('sqlite::memory:'))
];

foreach ($caches as $cache) {
    test_cache($cache);
class CacheTest extends TestCase
{
    public function test_cache()
    {
        $caches = [
            new ArrayCache,
            new PdoCache(new \PDO('sqlite::memory:'))
        ];
        foreach ($caches as $cache) {
            $this->assertNull($cache->get('foo'));
            $this->assertSame('default', $cache->get('foo', 'default'));

            $cache->set('foo', 'bar');
            $this->assertSame('bar', $cache->get('foo'));
            $this->assertSame('bar', $cache->get('foo', 'default'));

            $cache->set('is_cool');
            $this->assertTrue($cache->get('is_cool'));

            $cache->set('is_bro', false);
            $this->assertFalse($cache->get('is_bro'));

            $cache->delete('foo');
            $this->assertNull($cache->get('foo'));

            $cache->clear();
            $this->assertNull($cache->get('is_bro'));
        }
    }
}
\ No newline at end of file

A tests/ContainerTest.php => tests/ContainerTest.php +28 -0
@@ 0,0 1,28 @@
<?php

declare(strict_types=1);

namespace Thirdplace;

use PHPUnit\Framework\TestCase;

class ContainerTest extends TestCase
{
    public function test()
    {
        $sut = new Container();

        $sut['foo'] = 'bar';
        $this->assertSame('bar', $sut['foo']);

        $sut['two'] = fn() => 2;
        $this->assertSame(2, $sut['two']);
        $this->assertSame(2, $sut['two']);

        $sut['test'] = fn($c) => $c['two'];
        $this->assertSame(2, $sut['test']);

        $this->expectException(\Exception::class);
        $sut['foo'] = 'bar';
    }
}
\ No newline at end of file

A tests/RouterTest.php => tests/RouterTest.php +33 -0
@@ 0,0 1,33 @@
<?php
declare(strict_types=1);

namespace Thirdplace;

use PHPUnit\Framework\TestCase;

class RouterTest extends TestCase
{
    public function test()
    {
        $sut = new Router;

        $this->assertSame([Router::NOT_FOUND, [], []], $sut->dispatch('GET', '/'));

        $sut->addRoute('GET', '/', 'fn1');
        $this->assertSame([Router::FOUND, 'fn1', []], $sut->dispatch('GET', '/'));

        $sut->addRoute('GET', '/foo/bar', 'fn2');
        $this->assertSame([Router::FOUND, 'fn2', []], $sut->dispatch('GET', '/foo/bar'));

        $sut->addRoute(['GET', 'POST'], '/api', 'fn3');
        $this->assertSame([Router::FOUND, 'fn3', []], $sut->dispatch('GET', '/api'));
        $this->assertSame([Router::FOUND, 'fn3', []], $sut->dispatch('POST', '/api'));
        $this->assertSame([Router::METHOD_NOT_ALLOWED, [], []], $sut->dispatch('DELETE', '/api'));

        $sut->addRoute('GET', '/account/(\d+)', 'fn4');
        $this->assertSame([Router::FOUND, 'fn4', ['2']], $sut->dispatch('GET', '/account/2'));

        $sut->addRoute('GET', '/foo/(\d+)/(\w+)', 'fn5');
        $this->assertSame([Router::FOUND, 'fn5', ['2', 'two']], $sut->dispatch('GET', '/foo/2/two'));
    }
}
\ No newline at end of file

D tests/container.php => tests/container.php +0 -20
@@ 1,20 0,0 @@
<?php
declare(strict_types=1);

namespace Thirdplace;

$sut = new Container();

$sut['foo'] = 'bar';
assertSame('bar', $sut['foo']);

$sut['two'] = fn() => 2;
assertSame(2, $sut['two']);
assertSame(2, $sut['two']);

$sut['test'] = fn($c) => $c['two'];
assertSame(2, $sut['test']);

expectException(function() use ($sut) {
    $sut['foo'] = 'bar';
});

D tests/http.php => tests/http.php +0 -10
@@ 1,10 0,0 @@
<?php
declare(strict_types=1);

namespace Thirdplace;

//$sut = Cookie::fromString('foo=bar');
//assertEquals(['foo', 'bar'], [$sut->name, $sut->value]);

$sut = Cookie::fromString('; foo=bar;');
assertEquals(['foo', 'bar'], [$sut->name, $sut->value]);
\ No newline at end of file

D tests/router.php => tests/router.php +0 -25
@@ 1,25 0,0 @@
<?php
declare(strict_types=1);

namespace Thirdplace;

$sut = new Router;

assertSame([Router::NOT_FOUND, [], []], $sut->dispatch('GET', '/'));

$sut->addRoute('GET', '/', 'fn1');
assertSame([Router::FOUND, 'fn1', []], $sut->dispatch('GET', '/'));

$sut->addRoute('GET', '/foo/bar', 'fn2');
assertSame([Router::FOUND, 'fn2', []], $sut->dispatch('GET', '/foo/bar'));

$sut->addRoute(['GET', 'POST'], '/api', 'fn3');
assertSame([Router::FOUND, 'fn3', []], $sut->dispatch('GET', '/api'));
assertSame([Router::FOUND, 'fn3', []], $sut->dispatch('POST', '/api'));
assertSame([Router::METHOD_NOT_ALLOWED, [], []], $sut->dispatch('DELETE', '/api'));

$sut->addRoute('GET', '/account/(\d+)', 'fn4');
assertSame([Router::FOUND, 'fn4', ['2']], $sut->dispatch('GET', '/account/2'));

$sut->addRoute('GET', '/foo/(\d+)/(\w+)', 'fn5');
assertSame([Router::FOUND, 'fn5', ['2', 'two']], $sut->dispatch('GET', '/foo/2/two'));
\ No newline at end of file

D tests/url.php => tests/url.php +0 -26
@@ 1,26 0,0 @@
<?php
declare(strict_types=1);

namespace Thirdplace;

assertEquals('https://example.com:81/foo?bar=baz#kek', (string)url('https://example.com:81/foo?bar=baz#kek'));
assertEquals('https://example.com', (string)url('https://example.com'));
assertEquals('https://example.com/foo', (string)url('https://example.com/foo'));
assertEquals('https://example.com/foo/', (string)url('https://example.com/foo/'));
assertEquals('https://example.com', (string)url('https://example.com/'));
assertEquals('https://example.com', (string)url('https://example.com//'));
assertEquals('https://example.com', (string)url('https://example.com/./'));
assertEquals('https://example.com', (string)url('https://example.com//./'));
assertEquals('https://example.com', (string)url('https://example.com/.///'));
assertEquals('https://example.com/a', (string)url('https://example.com/../a'));
assertEquals('https://example.com', (string)url('https://example.com/foo/..'));
assertEquals('https://example.com/foo', (string)url('https://example.com/foo/bar/..'));
assertEquals('https://example.com/foo?bar=', (string)url('https://example.com/foo?bar'));
assertEquals('https://example.com/?baz=', (string)url('https://example.com/foo/..?baz='));
assertEquals('https://example.com/foo/bar', (string)url('https://example.com/foo/bar/baz/..'));
assertEquals('https://example.com/foo', (string)url('https://example.com/foo/bar/../baz/..'));
//assertEquals('https://example.com', (string)url('https://example.com/foo/bar/../..'));
assertEquals('https://example.com', (string)url('https://example.com/../..'));
assertEquals('https://example.com/foo?foo=bar', (string) url('https://example.com/foo?foo=bar'));
assertEquals('https://blog.zulip.com/2022/05/05/public-access-option', (string) url('https://blog.zulip.com/2022/05/05/public-access-option'));
assertEquals('https://kevincox.ca/feed.atom', (string) url('https://kevincox.ca/2022/05/../../feed.atom'));