user = Helper::makeUser('twig.' . randomString(6), 'twig'); $this->user->setField('PermissionID', SYSOP)->modify(); } public function tearDown(): void { $this->user->remove(); } protected static function twig(string $template): \Twig\TemplateWrapper { return Util\Twig::factory(new Manager\User())->createTemplate($template); } public function testDominator(): void { new Util\Dominator()->flush(); // in case anything has already been set $twig = Util\Twig::factory(new Manager\User()); $twig->createTemplate("{{ dom.click('#id', \"$('#id').frob(); return false;\") }}")->render(); $expected = <<document.addEventListener('DOMContentLoaded', () => { \$('#id').click(() => {\$('#id').frob(); return false;}); }) END; $this->assertEquals($expected, $twig->createTemplate('{{ dom.emit|raw }}')->render(), 'twig-dominator'); } public function testFilter(): void { $article = self::twig('{{ word|article }}'); $this->assertEquals('a', $article->render(['word' => 'consonant']), 'twig-article-a'); $this->assertEquals('an', $article->render(['word' => 'anticonsonant']), 'twig-article-an'); $this->assertEquals('YmxvYg', self::twig('{{ b|b64 }}')->render(['b' => 'blob']), 'twig-b64'); // badge_list is weird $this->assertEquals( 'bold', self::twig('{{ text|bb_format }}')->render(['text' => '[b]bold[/b]']), 'twig-bb-format' ); if (IMAGE_CACHE_ENABLED) { $this->assertStringStartsWith( '' . IMAGE_CACHE_HOST . '/f/full/',
                self::twig('{{ text|bb_forum }}')->render(['text' => '[img=https://example.com/image.jpg]']),
                'twig-bb-forum'
            );
        } else {
            $this->assertStringStartsWith(
                '<img loading=render(['text' => '[img=https://example.com/image.jpg]']), 'twig-bb-forum' ); } $checked = self::twig('{%- from "macro/form.twig" import checked -%}'); $this->assertEquals( '', $checked->render(['truth' => true]), 'twig-checked-true' ); $this->assertEquals( '', $checked->render(['truth' => false]), 'twig-checked-false' ); $sth = new Util\SortableTableHeader('alpha', [ 'alpha' => ['dbColumn' => 'one', 'defaultSort' => 'desc', 'text' => 'First'], 'beta' => ['dbColumn' => 'two', 'defaultSort' => 'desc', 'text' => 'Second'], ]); $heading = self::twig('{{ heading|column("alpha") }}'); $this->assertEquals( 'First ↑', $heading->render(['heading' => $sth]), 'twig-heading' ); $linkify = self::twig('{{ url|linkify }}'); $this->assertEquals( '/index.php', $linkify->render(['url' => '/index.php']), 'twig-linkify-index' ); $this->assertEquals( '' . SITE_URL . '/index.php', $linkify->render(['url' => SITE_URL . '/index.php']), 'twig-linkify-site-index' ); $this->assertEquals('3.00 MiB', self::twig('{{ size|octet_size }}')->render(['size' => 1024 * 1024 * 3]), 'twig-octet-3.00MiB'); $this->assertEquals('3 MiB', self::twig('{{ size|octet_size(0) }}')->render(['size' => 1024 * 1024 * 3]), 'twig-octet-3MiB'); $this->assertEquals('3.675 GiB', self::twig('{{ size|octet_size(3) }}')->render(['size' => 1024 * 1024 * 1024 * 3.675]), 'twig-octet-3.675GiB'); $this->assertEquals('3.68 GiB', self::twig('{{ size|octet_size(2) }}')->render(['size' => 1024 * 1024 * 1024 * 3.675]), 'twig-octet-3.68GiB'); $this->assertEquals('3.7 GiB', self::twig('{{ size|octet_size(1) }}')->render(['size' => 1024 * 1024 * 1024 * 3.675]), 'twig-octet-3.7GiB'); $this->assertEquals('4 GiB', self::twig('{{ size|octet_size(0) }}')->render(['size' => 1024 * 1024 * 1024 * 3.675]), 'twig-octet-4GiB'); $this->assertEquals('', self::twig('{{ number|plural }}')->render(['number' => 1]), 'twig-plural-1'); $this->assertEquals('s', self::twig('{{ number|plural }}')->render(['number' => 2]), 'twig-plural-2'); $this->assertEquals('es', self::twig('{{ number|plural("es") }}')->render(['number' => 3]), 'twig-plural-3'); $this->assertEquals('', self::twig('{{ value|repeat(0) }}')->render(['value' => 'go']), 'twig-repeat-0'); $this->assertEquals('gogo', self::twig('{{ value|repeat(2) }}')->render(['value' => 'go']), 'twig-repeat-2'); $this->assertEquals('gogogogogo', self::twig('{{ value|repeat(5) }}')->render(['value' => 'go']), 'twig-repeat-5'); $this->assertEquals('gogogo', self::twig('{{ value|repeat(n) }}')->render(['value' => 'go', 'n' => 3]), 'twig-repeat-3'); $this->assertEquals('abc "def"…', self::twig('{{ value|shorten(10) }}')->render(['value' => 'abc "def" ghi']), 'twig-shorten-10'); $this->assertEquals( '2 hours and 7 mins', self::twig('{{ value|time_interval }}')->render(['value' => 3600 * 2 + 7 * 60]), 'twig-time-ago' ); $this->assertEquals( '30 mins', self::twig('{{ value|time_interval }}')->render(['value' => 1800.125]), 'twig-time-float-ago' ); $this->assertMatchesRegularExpression( '@^1 hour ago$@', self::twig('{{ value|time_diff }}')->render(['value' => date("Y-m-d H:i:s", strtotime("-1 hour"))]), 'twig-time-diff' ); $this->assertEquals( '2h7s', self::twig('{{ value|time_compact }}')->render(['value' => 3600 * 2 + 7]), 'twig-time-interval' ); $this->assertEquals( '2', self::twig('{{ value|token_count }}')->render([ 'value' => new Manager\SiteOption()->freeTokenSize() * 2 - 1 ]), 'twig-token-count-2' ); $this->assertEquals( '3', self::twig('{{ value|token_count }}')->render([ 'value' => new Manager\SiteOption()->freeTokenSize() * 2 + 1 ]), 'twig-token-count-3' ); $this->assertEquals('✅', self::twig('{{ value|truth }}')->render(['value' => true]), 'twig-truth-true'); $this->assertEquals('❌', self::twig('{{ value|truth }}')->render(['value' => false]), 'twig-truth-false'); $this->assertEquals('First time', self::twig('{{ value|ucfirst }}')->render(['value' => 'first time']), 'twig-ucfirst-1'); $this->assertEquals('A Day In The Life', self::twig('{{ value|ucfirstall }}')->render(['value' => 'a day in the life']), 'twig-ucfirstall'); Util\Twig::setViewer($this->user); $this->assertEquals( "user->id}\">{$this->user->username()}", self::twig('{{ user_id|user_url }}')->render(['user_id' => $this->user->id]), 'twig-user-url' ); $donor = new User\Donor($this->user); $donor->donate( amount: 20, xbtRate: 0.05, source: 'phpunit twig source', reason: 'phpunit twig reason', ); $this->assertMatchesRegularExpression( "@^user->id}\">{$this->user->username()} \(Sysop\)$@", self::twig('{{ user_id|user_full }}')->render(['user_id' => $this->user->id]), 'twig-user-full' ); $status = self::twig('{{ user_id|user_status(viewer) }}'); $this->assertEquals( "", $status->render(['user_id' => $this->user->id, 'viewer' => $this->user]), 'twig-user-status' ); $donor->remove(); Helper::flushDonationMonth(1); } public function testImageCache(): void { $imageCache = self::twig('{{ image|image_cache }}'); $this->assertStringStartsWith( IMAGE_CACHE_HOST . '/i/full/', $imageCache->render(['image' => 'https://example.com/image.url']), 'twig-image-full-enabled' ); $imageCache = self::twig('{{ image|image_cache(height, width) }}'); $this->assertStringStartsWith( IMAGE_CACHE_HOST . '/i/64x96/', $imageCache->render(['image' => 'https://example.com/image.url', 'height' => 64, 'width' => 96]), 'twig-image-64x96' ); $this->assertStringStartsWith( IMAGE_CACHE_HOST . '/i/256x/', $imageCache->render(['image' => 'https://example.com/image.url', 'height' => 256]), 'twig-image-height-256' ); $this->assertStringStartsWith( IMAGE_CACHE_HOST . '/i/x480/', $imageCache->render(['image' => 'https://example.com/image.url', 'width' => 480]), 'twig-image-width-480' ); $this->assertStringStartsWith( '' . IMAGE_CACHE_HOST . '/f/full/',
            self::twig('{{ text|bb_forum }}')->render(['text' => '[img=https://example.com/image.jpg]']),
            'twig-bb-forum'
        );
    }

    public function testImageProxy(): void {
        $imageCache = self::twig('{{ image|image_proxy(active) }}');
        $image = $imageCache->render(['image' => 'https://example.com/image.url', 'active' => true]);
        $this->assertStringStartsWith(IMAGE_CACHE_HOST . '/i/full/', $image, 'twig-image-proxy-spec');
        $this->assertStringEndsWith('/proxy', $image, 'twig-image-proxy-end');

        $image = $imageCache->render(['image' => 'https://example.com/image.url', 'active' => false]);
        $this->assertStringEndsNotWith('/proxy', $image, 'twig-image-no-proxy-end');
    }

    public function testFunction(): void {
        Base::setRequestContext(new RequestContext('/index.php', '127.0.0.1', ''));
        Util\Twig::setViewer($this->user);
        $this->assertStringStartsWith('<!DOCTYPE html>', self::twig('{{ header(render(), 'twig-function-header'); $current = new User\Session($this->user)->create([ 'keep-logged' => '0', 'browser' => [ 'Browser' => 'phpunit', 'BrowserVersion' => '1.0', 'OperatingSystem' => 'phpunit/OS', 'OperatingSystemVersion' => '1.0', ], 'ipaddr' => '127.0.0.1', 'useragent' => 'phpunit', ]); global $SessionID; $SessionID = $current['SessionID']; $this->assertStringEndsWith("\n\n", self::twig('{{ footer() }}')->render(), 'twig-function-footer'); $this->assertEquals( 'http://localhost/?arg=abc', self::twig('{{ build_url("http://localhost/", {"arg": arg}) }}') ->render(['arg' => 'abc']), 'twig-function-build-url-1' ); $this->assertEquals( 'http://localhost/?arg=abc&id=10', self::twig('{{ build_url("http://localhost/", {"arg": arg, "id": id}) }}') ->render(['arg' => 'abc', 'id' => 10]), 'twig-function-build-url-2' ); $this->assertEquals( 'http://localhost/index?x=123&arg=def', self::twig('{{ build_url("http://localhost/index?x=123", {"arg": arg}) }}') ->render(['arg' => 'def']), 'twig-function-build-url-3' ); $this->assertEquals( 'http://localhost/index?x=123&arg=def&id=20', self::twig('{{ build_url("http://localhost/index?x=123", {"arg": arg, "id": id}) }}') ->render(['arg' => 'def', 'id' => 20]), 'twig-function-build-url-4' ); $this->assertEquals( '127.0.0.1 S', self::twig('{{ ipaddr(ip) }}')->render(['ip' => '127.0.0.1']), 'twig-function-ipaddr' ); // these will fail many, many, many years from now $this->assertEquals(6, strlen(self::twig('{{ mtime(asset) }}')->render(['asset' => 'blank.gif'])), 'twig-function-mtime-file'); $this->assertEquals(6, strlen(self::twig('{{ mtime_scss(asset) }}')->render(['asset' => 'global.scss'])), 'twig-function-mtime-scss'); $this->assertEquals( '2.00', self::twig('{{ ratio(up, down) }}')->render(['up' => 100, 'down' => 50]), 'twig-function-ratio-2' ); $this->assertEquals( '1.00', self::twig('{{ ratio(up, down) }}')->render(['up' => 100, 'down' => 100]), 'twig-function-ratio-1' ); $this->assertEquals( '0.50', self::twig('{{ ratio(up, down) }}')->render(['up' => 50, 'down' => 100]), 'twig-function-ratio-0.5' ); $this->assertEquals( '0.00', self::twig('{{ ratio(up, down) }}')->render(['up' => 0, 'down' => 100]), 'twig-function-ratio-0' ); $this->assertEquals( '∞', self::twig('{{ ratio(up, down) }}')->render(['up' => 50, 'down' => 0]), 'twig-function-ratio-infin' ); $this->assertEquals(120, self::twig('{{ upscale(value) }}')->render(['value' => 120]), 'twig-upscale-120'); $this->assertEquals(130, self::twig('{{ upscale(value) }}')->render(['value' => 121]), 'twig-upscale-121'); $this->assertEquals(130, self::twig('{{ upscale(value) }}')->render(['value' => 129]), 'twig-upscale-129'); $this->assertEquals(48000, self::twig('{{ upscale(value) }}')->render(['value' => 47133]), 'twig-upscale-47133'); } public function testTest(): void { $this->assertEquals('yes', self::twig('{% if value is nan %}yes{% endif %}')->render(['value' => sqrt(-1)]), 'twig-test-nan'); $this->assertEquals( 'yes', self::twig('{% if value is request_fill %}yes{% endif %}')->render(['value' => new Contest\RequestFill(0, '', '')]), 'twig-test-contest-request-fill' ); } }