diff --git a/.github/workflows/code_coverage.yaml b/.github/workflows/code_coverage.yaml new file mode 100644 index 0000000..c04db4c --- /dev/null +++ b/.github/workflows/code_coverage.yaml @@ -0,0 +1,46 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +name: "Code Coverage" + +on: + pull_request: + push: + branches: + - "main" + +permissions: + contents: "read" + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + code-coverage: + name: "Code Coverage" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v7" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "pcov" + php-version: "8.5" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v4" + + - name: "Tests with coverage" + run: "vendor/bin/phpunit --coverage-clover build/logs/clover.xml" + + - name: "Upload to Codecov" + uses: "codecov/codecov-action@v7" + with: + token: "${{ secrets.CODECOV_TOKEN }}" # not required for public repos + files: "build/logs/clover.xml" + flags: "unittests" + fail_ci_if_error: true diff --git a/CLAUDE.md b/CLAUDE.md index 8a0e01e..714cc67 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,9 @@ vendor/bin/phpcs # coding standard (brainbits/phpcs-standa CI (`.github/workflows/build.yml`) runs composer validate/audit, phpcs and phpstan on PHP 8.5 and the tests on PHP 8.4 and 8.5 with lowest and highest dependencies. phpcs only runs with the highest dependencies; the lowest -coding standard version reports different violations. +coding standard version reports different violations. `.github/workflows/code_coverage.yaml` uploads coverage (pcov, +PHP 8.5) to Codecov. Line coverage is 100 %; `#[CoversFunction]` only counts lines of the named function, so internal +helpers need their own `Internal*Test`. ## How functions are built diff --git a/README.md b/README.md index 1c08867..a5fc0ee 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # fnc +[![Coverage Status](https://img.shields.io/codecov/c/github/temp/fnc)](https://app.codecov.io/gh/temp/fnc) + A [Ramda](https://ramdajs.com/)-inspired functional programming library for PHP. All functions live in the `Fnc` namespace, are plain functions (no classes) and most of them are @@ -30,8 +32,8 @@ $adultNames([ composer require temp/fnc ``` -The package was called `temp/fn` up to 1.0, so versions 1.0 and older are only available under that name. `temp/fnc` -replaces `temp/fn`, Composer never installs both. The namespace `Fnc` is unchanged. +The package was called `temp/fn` up to 1.0 and is abandoned in favour of `temp/fnc`, which also provides the older +versions. `temp/fnc` replaces `temp/fn`, Composer never installs both. The namespace `Fnc` is unchanged. Requires PHP 8.4 or later. The functions are loaded via Composer's `files` autoloading, so importing them with `use function Fnc\…;` is all you need. diff --git a/tests/Unit/Curry5Test.php b/tests/Unit/Curry5Test.php index 3723311..316381e 100644 --- a/tests/Unit/Curry5Test.php +++ b/tests/Unit/Curry5Test.php @@ -33,5 +33,6 @@ public function testCurry3(): void $this->assertEquals(17, $sum4(5, 10, -4)(4, 2)); $this->assertEquals(17, $sum4(5, 10, -4, 4, 2)); $this->assertEquals(17, $sum4(5, 10, -4, 4, 2, 8)); + $this->assertEquals(17, $sum4(5, 10, -4, 4)(2)); } } diff --git a/tests/Unit/InternalAcceptsArgsTest.php b/tests/Unit/InternalAcceptsArgsTest.php new file mode 100644 index 0000000..f084dc5 --- /dev/null +++ b/tests/Unit/InternalAcceptsArgsTest.php @@ -0,0 +1,46 @@ + $a; + + $this->assertTrue(_acceptsArgs($fn, 1)); + $this->assertTrue(_acceptsArgs($fn, 2)); + $this->assertFalse(_acceptsArgs($fn, 3)); + } + + public function testCurriedClosureUsesRegisteredArity(): void + { + $curried = curry2(static fn ($a, $b) => $a); + + $this->assertTrue(_acceptsArgs($curried, 2)); + $this->assertFalse(_acceptsArgs($curried, 3)); + } + + public function testVariadic(): void + { + $this->assertTrue(_acceptsArgs(static fn (...$args) => $args, 5)); + } + + public function testInternalFunctionCountsRequiredParametersOnly(): void + { + // trim() has an optional $characters parameter that must not receive a key + $this->assertTrue(_acceptsArgs('trim', 1)); + $this->assertFalse(_acceptsArgs('trim', 2)); + } +} diff --git a/tests/Unit/InternalReflectCallableTest.php b/tests/Unit/InternalReflectCallableTest.php new file mode 100644 index 0000000..445dcc4 --- /dev/null +++ b/tests/Unit/InternalReflectCallableTest.php @@ -0,0 +1,27 @@ +assertSame('trim', _reflectCallable('trim')->getName()); + } + + public function testNonCallableThrows(): void + { + $this->expectException(RuntimeException::class); + + _reflectCallable('not a function'); + } +} diff --git a/tests/Unit/SplitTest.php b/tests/Unit/SplitTest.php index 71b3662..a08c88f 100644 --- a/tests/Unit/SplitTest.php +++ b/tests/Unit/SplitTest.php @@ -4,6 +4,7 @@ namespace FncTests\Unit; +use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversFunction; use PHPUnit\Framework\TestCase; @@ -82,4 +83,11 @@ public function testWithMultipleMixedElements(): void $this->assertSame(['foo', 'bar', '1', '2.5'], $result); } + + public function testEmptySeparatorThrows(): void + { + $this->expectException(InvalidArgumentException::class); + + split('', 'foo'); + } }