diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..ac3c877 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: json, simplexml + coverage: none + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist + + - name: Run tests + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7aceac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.phpunit.result.cache diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0c435e4 --- /dev/null +++ b/composer.json @@ -0,0 +1,12 @@ +{ + "name": "unsupervised/freshrss-bluesky-threads", + "description": "FreshRSS extension: expand Bluesky posts into full reply threads", + "type": "freshrss-extension", + "license": "AGPL-3.0-or-later", + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.5 || ^11.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..e1e7644 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1802 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b803efbfed6dedae0000808f3c73da71", + "packages": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.46" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-12-24T07:01:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.55", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:37:06+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:26:40+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/extension.php b/extension.php index f582b7d..d81e528 100644 --- a/extension.php +++ b/extension.php @@ -5,7 +5,7 @@ declare(strict_types=1); final class BlueskyThreadsExtension extends Minz_Extension { private const API_BASE = 'https://public.api.bsky.app/xrpc'; - private const POST_URL_PATTERN = '#bsky\.app/profile/([^/]+)/post/([^/?#\s]+)#'; + private const POST_URL_PATTERN = '~bsky\.app/profile/([^/]+)/post/([^/?#\s]+)~'; // How long a cached thread is considered fresh, based on the post's age. // Posts older than FREEZE_AGE are never re-fetched. diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..19550b6 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,11 @@ + + + + + tests + + + diff --git a/tests/BlueskyThreadsTest.php b/tests/BlueskyThreadsTest.php new file mode 100644 index 0000000..5ab2e6e --- /dev/null +++ b/tests/BlueskyThreadsTest.php @@ -0,0 +1,305 @@ +ext = new BlueskyThreadsExtension(); + } + + /** Call a private/protected method by name and return its result. */ + private function call(string $method, mixed ...$args): mixed { + $ref = new ReflectionMethod(BlueskyThreadsExtension::class, $method); + return $ref->invoke($this->ext, ...$args); + } + + // ------------------------------------------------------------------------- + // Feed parsing — verifies the extension detects Bluesky posts in a real feed + // ------------------------------------------------------------------------- + + /** + * Every in the hockeyviz RSS feed must match the Bluesky post URL + * pattern used by the extension, confirming the feed is fully covered. + * + * Feed source: https://bsky.app/profile/did:plc:d4324t32vfi5xzydqbh2qdj3/rss + */ + public function testAllFeedLinksMatchBskyPattern(): void { + $feed = simplexml_load_file(__DIR__ . '/fixtures/hockeyviz_feed.xml'); + $pattern = '~bsky\.app/profile/([^/]+)/post/([^/?#\s]+)~'; + + $this->assertNotFalse($feed, 'RSS fixture must be parseable XML'); + $this->assertGreaterThan(0, count($feed->channel->item), 'Feed must have at least one item'); + + foreach ($feed->channel->item as $item) { + $this->assertMatchesRegularExpression( + $pattern, + (string) $item->link, + "Link '{$item->link}' should match the Bluesky post URL pattern" + ); + } + } + + /** + * The specific thread post (2 replies) is present in the feed and its URL + * is correctly identified as a Bluesky post by the extension's pattern. + */ + public function testThreadPostIsPresentInFeed(): void { + $feed = simplexml_load_file(__DIR__ . '/fixtures/hockeyviz_feed.xml'); + $threadPostLink = 'https://bsky.app/profile/hockeyviz.com/post/3mhtk7awhrp26'; + + $found = false; + foreach ($feed->channel->item as $item) { + if ((string) $item->link === $threadPostLink) { + $found = true; + break; + } + } + + $this->assertTrue($found, "Thread post {$threadPostLink} must be present in the feed fixture"); + } + + /** + * fetchThread() leaves entries whose link is not a Bluesky URL completely + * untouched — no content mutation should occur. + */ + public function testFetchThreadIgnoresNonBskyEntries(): void { + $entry = new FreshRSS_Entry(); + $entry->_link('https://example.com/some/article'); + $entry->_content('original content'); + + $result = $this->ext->fetchThread($entry); + + $this->assertSame('original content', $result->content()); + } + + /** + * fetchThread() recognises a valid Bluesky post URL and attempts enrichment. + * We verify it does NOT leave the content untouched (even if the API fails + * in CI, the URL detection code-path must be exercised). + * + * Because this calls the live API (or silently skips on failure), we just + * assert the entry is returned without throwing. + */ + public function testFetchThreadHandlesBskyEntryWithoutThrowing(): void { + $entry = new FreshRSS_Entry(); + $entry->_link('https://bsky.app/profile/hockeyviz.com/post/3mhtk7awhrp26'); + $entry->_content(''); + + $result = $this->ext->fetchThread($entry); + + $this->assertInstanceOf(FreshRSS_Entry::class, $result); + } + + // ------------------------------------------------------------------------- + // Thread rendering — given a fixture API response, HTML must be correct + // ------------------------------------------------------------------------- + + /** + * renderThread() on the 3mhtk7awhrp26 fixture (1 root + 2 replies) must + * produce exactly 3 bsky-post elements and 1 bsky-replies wrapper. + */ + public function testRenderThreadWithTwoReplies(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhtk7awhrp26.json'), true); + $thread = $data['thread']; + + $html = $this->call('renderThread', $thread, true); + + $this->assertSame(3, substr_count($html, 'bsky-post'), + 'Should render root post + 2 replies = 3 bsky-post elements'); + + $this->assertSame(1, substr_count($html, 'bsky-replies'), + 'Should wrap replies in exactly one bsky-replies container'); + } + + /** Root post carries the bordered card style; replies use lighter padding. */ + public function testRootPostUsesCardStyle(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhtk7awhrp26.json'), true); + $html = $this->call('renderThread', $data['thread'], true); + + $this->assertStringContainsString('border:1px solid #cfd9de;border-radius:12px', $html, + 'Root post must use the card border style'); + } + + /** The root post text must appear in the rendered HTML. */ + public function testRootPostTextIsRendered(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhtk7awhrp26.json'), true); + $html = $this->call('renderThread', $data['thread'], true); + + $this->assertStringContainsString('Gold Drafting', $html); + $this->assertStringContainsString('hockeyviz.com', $html); + } + + /** Both reply authors must appear in the rendered HTML. */ + public function testReplyAuthorsAreRendered(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhtk7awhrp26.json'), true); + $html = $this->call('renderThread', $data['thread'], true); + + $this->assertStringContainsString('example.bsky.social', $html, 'First reply author must be present'); + $this->assertStringContainsString('hockeyfan.bsky.social', $html, 'Second reply author must be present'); + } + + /** The quoted/embedded post inside the root is rendered via renderQuotedRecord. */ + public function testQuotedPostEmbedIsRendered(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhtk7awhrp26.json'), true); + $html = $this->call('renderThread', $data['thread'], true); + + // The quoted record contains the #GoldRace text + $this->assertStringContainsString('GoldRace', $html, + 'Quoted post text must be rendered as an embed'); + } + + /** Every post must include a "View on Bluesky" link pointing to the right URL. */ + public function testViewOnBskyLinksArePresent(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhtk7awhrp26.json'), true); + $html = $this->call('renderThread', $data['thread'], true); + + $this->assertStringContainsString('3mhtk7awhrp26', $html, 'Root post rkey must appear in a Bluesky link'); + $this->assertStringContainsString('View on Bluesky', $html); + } + + // ------------------------------------------------------------------------- + // applyFacets — rich-text rendering + // ------------------------------------------------------------------------- + + public function testApplyFacetsNoFacetsEscapesHtml(): void { + $result = $this->call('applyFacets', '', []); + $this->assertSame('<Hello & World>', $result); + } + + public function testApplyFacetsRendersLink(): void { + $text = 'Visit example.com now'; + $facets = [[ + 'index' => ['byteStart' => 6, 'byteEnd' => 17], + 'features' => [['$type' => 'app.bsky.richtext.facet#link', 'uri' => 'https://example.com']], + ]]; + + $html = $this->call('applyFacets', $text, $facets); + + $this->assertStringContainsString('assertStringContainsString('example.com', $html); + } + + public function testApplyFacetsRendersMention(): void { + $text = 'Hello @user.bsky.social today'; + $facets = [[ + 'index' => ['byteStart' => 6, 'byteEnd' => 23], + 'features' => [['$type' => 'app.bsky.richtext.facet#mention', 'did' => 'did:plc:fakeid']], + ]]; + + $html = $this->call('applyFacets', $text, $facets); + + $this->assertStringContainsString('href="https://bsky.app/profile/did:plc:fakeid"', $html); + $this->assertStringContainsString('@user.bsky.social', $html); + } + + public function testApplyFacetsRendersHashtag(): void { + $text = 'Great game #GoldRace tonight'; + $facets = [[ + 'index' => ['byteStart' => 11, 'byteEnd' => 21], + 'features' => [['$type' => 'app.bsky.richtext.facet#tag', 'tag' => 'GoldRace']], + ]]; + + $html = $this->call('applyFacets', $text, $facets); + + $this->assertStringContainsString('href="https://bsky.app/hashtag/GoldRace"', $html); + $this->assertStringContainsString('#GoldRace', $html); + } + + // ------------------------------------------------------------------------- + // needsRefetch — staleness rules + // ------------------------------------------------------------------------- + + public function testNeedsRefetchWhenNeverCached(): void { + $this->assertTrue($this->call('needsRefetch', time() - 60, null), + 'Always refetch when there is no cache entry'); + } + + public function testNoRefetchWhenPostIsFrozen(): void { + $publishedAt = time() - (8 * 86400); // 8 days ago + $fetchedAt = time() - 3600; // fetched 1 hour ago + + $this->assertFalse($this->call('needsRefetch', $publishedAt, $fetchedAt), + 'Posts older than 7 days are frozen and must not be re-fetched'); + } + + public function testRefetchRecentPostWithStaleCache(): void { + $publishedAt = time() - 1800; // 30 min old → window is 10 min + $fetchedAt = time() - 900; // cached 15 min ago → stale + + $this->assertTrue($this->call('needsRefetch', $publishedAt, $fetchedAt), + 'Cache older than the refresh window must trigger a re-fetch'); + } + + public function testNoRefetchRecentPostWithFreshCache(): void { + $publishedAt = time() - 1800; // 30 min old → window is 10 min + $fetchedAt = time() - 300; // cached 5 min ago → fresh + + $this->assertFalse($this->call('needsRefetch', $publishedAt, $fetchedAt), + 'Cache newer than the refresh window must NOT trigger a re-fetch'); + } + + public function testRefetchWindows(): void { + $cases = [ + // [post age seconds, cache age seconds, expect refetch] + [1800, 601, true], // < 1 h post, > 10 min cache → stale + [1800, 599, false], // < 1 h post, < 10 min cache → fresh + [7200, 3601, true], // < 24 h post, > 1 h cache → stale + [7200, 3599, false], // < 24 h post, < 1 h cache → fresh + [3 * 86400, 43201, true], // < 7 d post, > 12 h cache → stale + [3 * 86400, 43199, false], // < 7 d post, < 12 h cache → fresh + ]; + + foreach ($cases as [$postAge, $cacheAge, $expectRefetch]) { + $publishedAt = time() - $postAge; + $fetchedAt = time() - $cacheAge; + $this->assertSame( + $expectRefetch, + $this->call('needsRefetch', $publishedAt, $fetchedAt), + "postAge={$postAge}s cacheAge={$cacheAge}s" + ); + } + } + + // ------------------------------------------------------------------------- + // handleConfigureAction — user config + // ------------------------------------------------------------------------- + + public function testHandleConfigureActionSavesDepth(): void { + Minz_Request::simulatePost(['depth' => '25']); + $this->ext->handleConfigureAction(); + + $this->assertSame(25, $this->ext->getUserConfigurationValue('depth')); + } + + public function testHandleConfigureActionClampsMinDepth(): void { + // 0 is falsy after (int) cast, so the code falls back to the default of 10. + // A negative value exercises the max(1, ...) clamp. + Minz_Request::simulatePost(['depth' => '-5']); + $this->ext->handleConfigureAction(); + + $this->assertSame(1, $this->ext->getUserConfigurationValue('depth')); + } + + public function testHandleConfigureActionClampsMaxDepth(): void { + Minz_Request::simulatePost(['depth' => '9999']); + $this->ext->handleConfigureAction(); + + $this->assertSame(1000, $this->ext->getUserConfigurationValue('depth')); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..77b1a0e --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,6 @@ + + + + + Math, hockey, viz, apocrypha + https://bsky.app/profile/hockeyviz.com + @hockeyviz.com - Micah McCurdy + + + https://bsky.app/profile/hockeyviz.com/post/3mhxruphxfz2q + No possible eliminations or qualifications today, will be a few days lull I expect. + 26 Mar 2026 14:11 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhxruphxfz2q + + + + https://bsky.app/profile/hockeyviz.com/post/3mhxnes7zvw2k + Yesterday's games: + 26 Mar 2026 12:50 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhxnes7zvw2k + + + + https://bsky.app/profile/hockeyviz.com/post/3mhxidqqzwc26 + The New York Rangers are the second team to be eliminated from playoff contention. + 26 Mar 2026 11:20 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhxidqqzwc26 + + + + https://bsky.app/profile/hockeyviz.com/post/3mhw7rraxo72t + The site being so old is weird to me now because the old charts are annoying to me spiritually. + 25 Mar 2026 23:14 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhw7rraxo72t + + + + https://bsky.app/profile/hockeyviz.com/post/3mhvymqtksm2l + 25 Mar 2026 21:06 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhvymqtksm2l + + + + https://bsky.app/profile/hockeyviz.com/post/3mhvo7humyx2j + hockey viz dot com snubbed again [contains quote post or other embedded content] + 25 Mar 2026 18:00 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhvo7humyx2j + + + + https://bsky.app/profile/hockeyviz.com/post/3mhv7532iwp2p + If Toronto beat New York tonight in any fashion, then the Rangers will be eliminated from playoff contention. + 25 Mar 2026 13:30 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhv7532iwp2p + + + + https://bsky.app/profile/hockeyviz.com/post/3mhuuvbngrc2g + Yesterday's games: + 25 Mar 2026 10:27 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhuuvbngrc2g + + + + https://bsky.app/profile/hockeyviz.com/post/3mhtt6lqhsk2b + Moms in the crowd smiling when their kids score their first goal >>> + 25 Mar 2026 00:24 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhtt6lqhsk2b + + + + https://bsky.app/profile/hockeyviz.com/post/3mhtq6pbhwn2p + Little-known HockeyViz feature that's fun on nights with lots of games. + 24 Mar 2026 23:30 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhtq6pbhwn2p + + + + + https://bsky.app/profile/hockeyviz.com/post/3mhtk7awhrp26 + Discussion about Gold Drafting always focusses on tanking, but really eliminating tanking discourse is a (very real) side-benefit. [contains quote post or other embedded content] + 24 Mar 2026 21:43 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhtk7awhrp26 + + + + https://bsky.app/profile/hockeyviz.com/post/3mhtjo3rtkn26 + The NHL, and many other leagues, would be improved by adopting Gold drafting. #GoldRace + 24 Mar 2026 21:33 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhtjo3rtkn26 + + + + https://bsky.app/profile/hockeyviz.com/post/3mhpqxoumjl2b + The Vancouver Canucks are the first team to be eliminated from playoff contention this season. + 23 Mar 2026 09:33 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhpqxoumjl2b + + + + https://bsky.app/profile/hockeyviz.com/post/3mhpqwxte4n2l + The Dallas Stars are the second team to qualify for the 2025-2026 playoffs. + 23 Mar 2026 09:33 +0000 + at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhpqwxte4n2l + + + diff --git a/tests/fixtures/thread_3mhtk7awhrp26.json b/tests/fixtures/thread_3mhtk7awhrp26.json new file mode 100644 index 0000000..51e8bfd --- /dev/null +++ b/tests/fixtures/thread_3mhtk7awhrp26.json @@ -0,0 +1,91 @@ +{ + "thread": { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": { + "uri": "at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhtk7awhrp26", + "cid": "bafyreicfake1", + "author": { + "did": "did:plc:d4324t32vfi5xzydqbh2qdj3", + "handle": "hockeyviz.com", + "displayName": "Micah McCurdy", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:d4324t32vfi5xzydqbh2qdj3/bafkreifake1@jpeg" + }, + "record": { + "$type": "app.bsky.feed.post", + "text": "Discussion about Gold Drafting always focusses on tanking, but really eliminating tanking discourse is a (very real) side-benefit. The primary benefit is that fans of bad teams get to enjoy the few late-season wins that they get, without having to add any games to the schedule.", + "createdAt": "2026-03-24T21:43:00.000Z", + "facets": [] + }, + "embed": { + "$type": "app.bsky.embed.record#view", + "record": { + "$type": "app.bsky.embed.record#viewRecord", + "uri": "at://did:plc:otherfake/app.bsky.feed.post/3mhtjo3rtkn26", + "author": { + "handle": "hockeyviz.com", + "displayName": "Micah McCurdy" + }, + "value": { + "text": "The NHL, and many other leagues, would be improved by adopting Gold drafting, which makes the existing schedule more exciting for fans, eliminates tanking discourse, and gives the best draft picks (on average) to the weakest teams, who need them the most. This is the 2025-2026 thread. #GoldRace", + "facets": [] + }, + "embeds": [] + } + }, + "replyCount": 2, + "repostCount": 4, + "likeCount": 31, + "indexedAt": "2026-03-24T21:43:01.000Z" + }, + "replies": [ + { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": { + "uri": "at://did:plc:replier1fake/app.bsky.feed.post/3mhtreplyone", + "cid": "bafyreicfake2", + "author": { + "did": "did:plc:replier1fake", + "handle": "example.bsky.social", + "displayName": "Reply Person One", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:replier1fake/bafkreifake2@jpeg" + }, + "record": { + "$type": "app.bsky.feed.post", + "text": "Great point about the side benefits! The tanking argument always dominates but you're right that it's secondary.", + "createdAt": "2026-03-24T21:55:00.000Z", + "facets": [] + }, + "replyCount": 0, + "repostCount": 1, + "likeCount": 5, + "indexedAt": "2026-03-24T21:55:01.000Z" + }, + "replies": [] + }, + { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": { + "uri": "at://did:plc:replier2fake/app.bsky.feed.post/3mhtreplytwo", + "cid": "bafyreicfake3", + "author": { + "did": "did:plc:replier2fake", + "handle": "hockeyfan.bsky.social", + "displayName": "Hockey Fan", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:replier2fake/bafkreifake3@jpeg" + }, + "record": { + "$type": "app.bsky.feed.post", + "text": "The fan experience angle is so underrated. Late-season games for bad teams actually matter under Gold.", + "createdAt": "2026-03-24T22:10:00.000Z", + "facets": [] + }, + "replyCount": 0, + "repostCount": 0, + "likeCount": 3, + "indexedAt": "2026-03-24T22:10:01.000Z" + }, + "replies": [] + } + ] + } +} diff --git a/tests/stubs/freshrss_stubs.php b/tests/stubs/freshrss_stubs.php new file mode 100644 index 0000000..4751e72 --- /dev/null +++ b/tests/stubs/freshrss_stubs.php @@ -0,0 +1,80 @@ +userConfig[$key] = $value; + } + + public function getUserConfigurationValue(string $key): mixed { + return $this->userConfig[$key] ?? null; + } +} + +class FreshRSS_Entry { + private string $link = ''; + private string $content = ''; + private int $date = 0; + + public function link(): string { return $this->link; } + + /** @param string $link */ + public function _link(string $link): void { $this->link = $link; } + + /** @param string $content */ + public function _content(string $content): void { $this->content = $content; } + + public function content(): string { return $this->content; } + + public function date(bool $asTimestamp = false): mixed { + return $asTimestamp ? $this->date : date('r', $this->date); + } + + public function _date(int $timestamp): void { $this->date = $timestamp; } +} + +class FreshRSS_EntryDao { + public function updateEntry(FreshRSS_Entry $entry): void {} +} + +class FreshRSS_Factory { + public static function createEntryDao(): FreshRSS_EntryDao { + return new FreshRSS_EntryDao(); + } +} + +class Minz_Request { + private static bool $isPost = false; + private static array $params = []; + + public static function isPost(): bool { return self::$isPost; } + + public static function paramString(string $key): string { + return self::$params[$key] ?? ''; + } + + /** Test helper: configure the fake request state. */ + public static function simulatePost(array $params): void { + self::$isPost = true; + self::$params = $params; + } + + public static function reset(): void { + self::$isPost = false; + self::$params = []; + } +}